前言
官方文档已经把语法和规则写得足够严谨,但初学者常遇到三个卡点:
- 结构体/枚举居然也能定义方法?
- mutating 到底"变异"了什么?
- static 与 class 关键字在类型方法里的区别与实战意义。
方法(Method)到底是什么
一句话:方法是"挂在某个类型上的函数"。
-
在 Swift 里,类(class)、结构体(struct)、枚举(enum)都能挂函数,有两大类,分别叫做实例方法或类型方法。
-
与 C/Objective-C 不同,C 只有函数指针,Objective-C 只有类能定义方法;Swift 把"方法"能力下放到了值类型,带来了更灵活的建模方式。
实例方法(Instance Method)
- 定义与调用
swift
class Counter1 {
var count = 0
// 实例方法:默认访问全部实例成员
func increment() {
count += 1
}
// 带参数的方法
func increment(by amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
// 调用
let counter = Counter1()
counter.increment() // 1
print(counter.count)
counter.increment(by: 5) // 6
print(counter.count)
counter.reset() // 0
print(counter.count)
- self 的隐式与显式
- 不写 self:编译器默认你访问的是"当前实例"成员。
- 必须写 self:局部变量/参数与属性重名时,用来消歧。
swift
struct Point {
var x = 0.0, y = 0.0
func isToTheRightOf(x: Double) -> Bool {
// 如果省略 self,x 会被当成参数 x
return self.x > x
}
}
值类型内部修改自身:mutating 实例方法
- 默认禁止修改
结构体/枚举是值类型,实例方法里不能改自己属性------除非加 mutating。
- 加 mutating 后发生了什么
- 方法被标记为"会改本体",编译器会把调用处生成的
let常量拦截掉。 - 底层实现:方法拿到的是
inout self,可以整体替换。
swift
struct Point: CustomStringConvertible {
var description: String {
"{ x: \(x), y: \(y)}"
}
var x = 0.0, y = 0.0
// 移动自身
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
// 更激进的写法:直接给 self 赋新实例
mutating func teleport(toX x: Double, toY y: Double) {
self = Point(x: x, y: y)
}
}
var p = Point(x: 1, y: 1)
print(p)
p.moveBy(x: 2, y: 3) // 现在 (3,4)
print(p)
p.teleport(toX: 0, toY: 0) // 整体替换为 (0,0)
print(p)
// 以下会编译错误
let fixedPoint = Point(x: 3, y: 3)
print(fixedPoint)
// fixedPoint.moveBy(x: 1, y: 1) // ❌
- 枚举也能 mutating
swift
enum TriStateSwitch {
case off, low, high
mutating func next() {
switch self {
case .off: self = .low
case .low: self = .high
case .high: self = .off
}
}
}
var oven = TriStateSwitch.low
print(oven)
oven.next() // high
print(oven)
oven.next() // off
print(oven)
类型方法(Type Method)
- 关键字
static:类、结构体、枚举都能用;子类不能重写。class:仅类能用;子类可 override。
- 调用方式
"类型名.方法名",无需实例。
- 方法体内 self 指"类型自身"
swift
class SomeClass {
class func helloType() {
print("Hello from \(self)") // 打印类名
}
}
SomeClass.helloType()
完整实战:游戏关卡管理
下面把"类型属性 + 类型方法 + 实例方法"揉到一起,演示一个常用模式:
- 类型层保存"全局状态"(最高解锁关卡)。
- 实例层保存"个人状态"(当前玩家在第几关)。
swift
struct LevelTracker {
// 1. 类型属性:所有玩家共享
nonisolated(unsafe) static var highestUnlockedLevel = 1
// 2. 类型方法:解锁
static func unlock(_ level: Int) {
if level > highestUnlockedLevel {
highestUnlockedLevel = level
}
}
// 3. 类型方法:查询是否解锁
static func isUnlocked(_ level: Int) -> Bool {
return level <= highestUnlockedLevel
}
// 4. 实例属性:个人当前关卡
var currentLevel = 1
// 5. 实例方法(mutating):进阶到指定关
@discardableResult
mutating func advance(to level: Int) -> Bool {
if LevelTracker.isUnlocked(level) {
currentLevel = level
return true
}
return false
}
}
// 玩家类
class Player {
let name: String
var tracker = LevelTracker()
init(name: String) {
self.name = name
}
// 完成某关
func complete(level: Int) {
LevelTracker.unlock(level + 1) // 全局解锁下一关
tracker.advance(to: level + 1) // 个人进度推进
}
}
// 场景脚本
let player = Player(name: "Argyrios")
player.complete(level: 1)
print("最高解锁关卡:\(LevelTracker.highestUnlockedLevel)") // 2
// 第二个玩家想跳关
let player2 = Player(name: "Beto")
if player2.tracker.advance(to: 6) {
print("直接跳到 6 成功")
} else {
print("6 关尚未解锁") // 走进这里
}
易忘细节速查表
- mutating 只能用于 struct/enum;class 天生可变,不需要。
- static 与 class 区别:
- 结构体/枚举只能用 static。
- 类里 static = final class,不允许子类覆盖;class 允许覆盖。
- 在类型方法里调用同类类型方法/属性,可直接写名字,无需前缀类型。
@discardableResult用于"调用方可以不处理返回值"的场景,消除警告。
总结与实战扩展
- 方法不再"是类的专利"后,优先用 struct 建模数据,再根据需要升级成 class,可大幅降低引用类型带来的共享状态问题。
- mutating 让"值语义 + 链式调用"成为可能,例如:
swift
extension Array where Element: Comparable {
mutating func removeMin() -> Element? {
guard let min = self.min() else { return nil }
remove(at: firstIndex(of: min)!)
return min
}
}
var scores = [98, 67, 84]
while let min = scores.removeMin() {
print("从低到高处理", min)
}
- 类型方法是做"全局状态但作用域受限"的利器:
- App 配置中心(static 存储 + 类型方法读写)
- 网络请求 stub 中心(type method 注册/注销 mock)
- 工厂方法(class func makeDefaultXxx())
- 与协议组合
把 mutating 写进协议,让 struct/enum 也能提供"可变更"接口,而 class 实现时自动忽略 mutating:
swift
protocol Resettable {
mutating func reset()
}