self 关键字
本教程共 93 篇 · 第 60 篇 · 更新于 2026-08-08 · 约 7 分钟阅读
本节目标:学完你理解 self 在每个实例或类型里自动指向”自己”,并会在参数名和属性名撞车时用它来区分。
self 是 Swift 里一个隐式存在的关键字,代表”当前的那个实例(或类型)自己”。绝大多数时候你根本不用写它,编译器会自动替你补上;但有一类场景它必不可少:当方法参数和属性同名,你得分清谁是谁。这一章把 self 的用法和边界讲透。
1-1 self 到底是什么
每一个类型的实例,内部都隐式带了一个 self 属性,它就等价于这个实例本身。你在实例方法里写 self,指的就是正在调用这个方法的那个对象。
class Counter {
var count = 0
func increment() {
self.count += 1
}
}
这里 self.count 明确表示”当前这个 Counter 实例的 count 属性”。但坦白说,前面第 59 章的 Counter 我们写的是 count += 1,没写 self 也跑得好好的。因为 Swift 有个贴心规则:当你在方法里用到一个已知属性或方法名时,如果没写 self,编译器默认就把它当成当前实例的成员。所以日常写代码,self 能省则省,代码更干净。
1-2 必须写 self 的场景:名字撞车
唯一绕不开 self 的情况是——方法参数名和实例属性名一模一样,此时参数名会”盖住”属性名,你必须用 self 把属性捞出来。
struct Point {
var x = 0.0, y = 0.0
func isToTheRightOf(x: Double) -> Bool {
return self.x > x
}
}
let somePoint = Point(x: 4.0, y: 5.0)
print(somePoint.isToTheRightOf(x: 1.0))
方法 isToTheRightOf(x:) 的参数叫 x,而结构体自己也有个属性 x。函数体里如果只写 x,Swift 会认为你指的是那个参数。要表达”我结构体的 x 属性”,就得写 self.x。少了 self 前缀,两个 x 就都指向参数,逻辑就错了。这种同名遮蔽在 Swift 里很常见,记住用 self 解围即可。
1-3 类型方法里的 self 指向类型
第 59 章提过,self 在类型方法里指的不是实例,而是类型本身。所以在类型方法内写 self.someTypeProperty,访问的是类型属性,用来和同名参数区分,规则和实例方法如出一辙。
struct Tracker {
static var total = 0
static func add(_ total: Int) {
self.total += total
}
}
self.total 明确指类型属性 total,参数 total 则是传进来的那个数。不写 self 的话,total += total 里的 total 全被当成参数,类型属性就永远加不上去。名字冲突时,self 是你的救命绳。
1-4 mutating 方法里给 self 整体换值
在值类型的 mutating 方法里,self 还能被直接赋一个新实例,从而整体替换原来的自己。
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
这个版本不逐个改属性,而是造了个新 Point 直接赋给 self。效果和上一种逐个改 x、y 完全一样,但写法不同。枚举的 mutating 方法也常用这招切 case:self = .low 之类。这里 self 不再只是”指代”,而是被当作可写的左值。
1-5 为什么 Swift 不强制写 self
你可能纳闷:别的语言经常要求 this.xxx,Swift 怎么这么随意?原因有两个。一是减少噪音,能省则省代码更易读;二是 Swift 的作用域规则足够聪明,在绝大多数上下文里它能准确判断你指的是属性还是局部变量。只有发生名字遮蔽这种歧义时,才需要你显式 self 来打破。
不过也有团队规范主张”一律写 self”,认为这样一眼能看出哪些是成员。这只是风格选择,语言层面不强制。作为初学者,我的建议是:平时省着写,只有撞名时才加,既清爽又不会埋坑。
1-6 和闭包捕获的小提醒
self 在闭包里是个需要警惕的地方。当闭包捕获 self 时,可能引发循环强引用,导致内存泄漏(第 77 章专讲)。那时你会看到 [weak self] 这种捕获列表,配合 self? 来安全访问。本章你只需记住:self 在普通方法里很安全、很自动;一旦进入闭包,就要多留个心眼。先把方法内的 self 用熟,闭包那套等学到内存管理自然就通了。
1-7 小结
self 就是”当前实例(或类型)自己”的代称,编译器大多时候帮你补全,不必手写。它真正出场只有两种情况:参数和属性同名时要消歧义,以及 mutating 方法里整体替换 self。理解这两点,self 对你就是透明又趁手的工具,而不是一团迷雾。
1-8 隐式 self 的推导边界
前面说编译器大多时候自动补 self,但有几个边界它会”装傻”,必须你手写。最典型就是名字遮蔽(参数和属性同名),这时编译器无法确定你指谁,默认偏向参数,所以要访问属性必须 self.。另一个边界是闭包捕获:闭包内部默认捕获不到实例的 self,得显式写 self 并配合捕获列表,这既是语法要求也是安全提醒。除此之外,属性名和局部变量重名时同理。
1-9 self 与初始化器的参数
Swift 的方法参数有”参数名”(体内用)和”参数标签”(调用用)。当参数名和属性名一样,比如 init(name: String) 里想给 self.name 赋值,你就得写 self.name = name。如果不写 self,等号右边和左边的 name 都会被当成参数,属性永远没被赋值。这类 bug 在初始化器里尤其多,因为初始化器大量出现”参数名等于属性名”的写法。养成习惯:初始化器里给属性赋值,只要参数同名就加 self,能省掉无数隐蔽错误。还有一个小技巧:如果你发现自己频繁写 self,先别急,也可以改参数名来避免遮蔽,比如把参数写成 newName 再 name = newName。但遮蔽有时不可避免,比如初始化器常沿用属性名做参数标签,此时 self 是更清晰的写法,反而不该回避。总之 self 是工具,用它能消歧义就用。
1-10 一个记忆口诀
记不住 self 何时必须写?给你个口诀:看到”同名遮蔽”就写 self,其余交给编译器。名字遮蔽发生在方法参数和属性撞名、或局部变量和属性撞名时。除此以外,Swift 的类型推断足够聪明,能分辨你指的是成员还是局部量。不少团队还要求一律写 self 以一眼区分成员,这是风格选择,不影响编译。作为初学者,先按”撞名才写”来,等熟练了再决定团队规范。