为什么要区分「函数」和「方法」
写 Swift 时,我们每天都在写 func。
但同一个关键字,有时叫「函数」,有时又叫「方法」。
名字不同,背后其实是作用域与归属权的差异:
- 函数(function)------独立存在,像一把瑞士军刀,谁都能拿来用。
- 方法(method)------寄居在类型内部,能直接访问类型的数据,像家电的遥控器,只能操控指定品牌。
搞清这一点,再读苹果文档或第三方库源码,就不会迷糊。
函数:真正独立的代码块
- 定义与调用
swift
// 函数定义:全局可用,不依赖任何类型
func greet(name: String) -> String {
return "Hello, \(name)!"
}
// 调用
let msg = greet(name: "Alice")
print(msg) // 输出:Hello, Alice!
- 生活化比喻
把函数想成微波炉:你把它搬到任何厨房(项目),它都能加热食物(完成任务)。
微波炉不隶属于某套房子(对象),完全独立。
- 再看一个无参无副作用的工具函数
swift
/// 返回当前时间戳(秒)
func currentTimestamp() -> TimeInterval {
return Date().timeIntervalSince1970
}
这种「纯工具」逻辑,做成函数最合适,因为任何类型都可能用得到。
方法:绑定在类型上的函数
方法 = 函数 + 上下文。
上下文就是类型(class/struct/enum)里的属性与其他方法。
- 实例方法(Instance Method)
swift
struct Car {
var speed: Int // 属性
// 实例方法:只能由具体 Car 实例调用
func describe() -> String {
return "The car is going \(speed) km/h."
}
}
let myCar = Car(speed: 100)
print(myCar.describe()) // 输出:The car is going 100 km/h.
要点:
describe写在Car内部,直接读取speed。- 如果脱离
Car实例,describe无法存在。
- 变异方法(Mutating Method)
值类型(struct/enum)默认不可修改自身属性,需要显式标记 mutating。
swift
struct Counter {
var count = 0
// 变异方法:可以修改属性
mutating func increment() {
count += 1
}
}
var counter = Counter()
counter.increment()
print(counter.count) // 输出:1
注意:
-
只有 值类型 才用
mutating;class 是引用类型,直接改即可。 -
调用者必须用
var声明,不能用let。 -
类型方法(Type Method)
相当于其他语言的「静态方法」,由类型本身调用,不依赖实例。
swift
class Car {
// 类型属性
static let defaultWheelCount = 4
// 类型方法
static func numberOfWheels() -> Int {
return defaultWheelCount
}
}
print(Car.numberOfWheels()) // 输出:4
使用场景:
- 工厂方法(创建实例)
- 与实例无关的全局配置或工具逻辑
函数与方法协同实战:跑步记录器
下面把「函数」与「方法」放在同一段业务里,感受它们如何各司其职。
swift
// 1️⃣ 独立函数:公里转英里,任何模块都能用
func convertToMiles(kilometers: Double) -> Double {
return kilometers * 0.621371
}
// 2️⃣ 类型:Runner
struct Runner {
var name: String
var distanceInKm: Double
// 实例方法:生成专属报告
func progressReport() -> String {
// 直接调用上面的独立函数
let miles = convertToMiles(kilometers: distanceInKm)
return "\(name) 今天跑了 \(String(format: "%.2f", miles)) 英里"
}
// 变异方法:增加跑量
mutating func runExtra(km: Double) {
distanceInKm += km
}
}
// 3️⃣ 使用
var emma = Runner(name: "Emma", distanceInKm: 5)
print(emma.progressReport()) // Emma 今天跑了 3.11 英里
emma.runExtra(km: 2)
print(emma.progressReport()) // Emma 今天跑了 4.35 英里
看到吗?
convertToMiles是纯逻辑,与任何类型无关。progressReport需要Runner内部数据,所以做成实例方法。- 两者组合,代码既复用又内聚。
易错点小结
| 场景 | 正确做法 | 常见错误 |
|---|---|---|
| struct 里想改属性 | 加 mutating |
忘记写,编译报错 |
| 类型方法里用实例属性 | ❌ 不能 | 把 static 当实例方法用 |
| 全局工具逻辑 | 写成函数 | 强行塞进某个类型,导致复用困难 |
理解 & 使用原则
-
优先写成函数:只要逻辑不依赖任何实例数据,就独立出来。
方便单元测试、跨模块复用,也减少类型体积。
-
升阶为方法的时机:
- 需要频繁访问类型内部私有属性
- 需要多态(子类重写)
- 需要链式调用(
return self)
满足一条,再考虑搬进类型。
-
命名统一,作用域清晰:
函数用「动词」开头,
convert、download、validate...方法也用「动词」,但可省略主语,
describe、increment、save...让调用者一眼看出谁属于谁。
扩展场景:把知识用到实际项目
- 网络层
swift
// 独立函数,任何模型都能调
func GET<T: Decodable>(_ url: URL) async throws -> T { ... }
// 模型内部方法,自己知道怎么拼装 URL
struct Article {
let id: Int
func detailURL() -> URL { ... }
}
- SwiftUI 视图
swift
// 全局函数,做颜色插值
func interpolateColor(from: Color, to: Color, percent: Double) -> Color { ... }
// 视图的方法,负责自己的业务
struct ProgressCircle: View {
var progress: Double
private func strokeColor() -> Color {
return interpolateColor(from: .green, to: .red, percent: progress)
}
}
-
算法库
快速排序、哈希函数等纯算法,一律写成函数,避免绑定具体类型。
结语
所有方法都是函数,但函数不一定是方法。
记住「是否依赖类型数据」这一把尺子,该独立就独立,该内聚就内聚。
当你把边界划清楚,代码会自然呈现出高内聚、低耦合的漂亮形态。
希望这篇笔记能帮你把「函数 vs 方法」彻底吃透,写出更 Swifty 的代码!