Swift 方法
方法是与某个特定类型(类、结构体或枚举)关联的函数,用来描述这个类型能做什么。
它与普通函数最大的区别是:方法要通过某个实例或类型来调用,并且可以直接访问该实例的属性。
实例方法
实例方法属于某个具体实例,可以读取和修改该实例的属性,也可以调用同类型的其他方法。
下面这个 Counter 类用三个实例方法管理一个计数器。
实例
class Counter {
var count = 0
func increment() {
count += 1
}
// 用 _ 显式省略外部标签,调用时直接传值
func incrementBy(_ amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
let counter = Counter()
counter.increment() // 计数值变成 1
counter.incrementBy(5) // 计数值变成 6
print("计数器的值:\(counter.count)")
counter.reset()
print("重置后的值:\(counter.count)")
运行结果:
计数器的值:6 重置后的值:0
实例方法只能被它所属类型的实例调用,不能脱离实例单独存在。
方法的参数标签规则
这是 Swift 初学者最容易写错的地方,必须先把规则记牢。
从 Swift 3 开始(SE-0046),所有参数默认都带外部标签,第一个参数也不例外。也就是说,func greet(site: String) 调用时要写成 greet(site: "RUNOOB")。
想让外部标签与参数名不同,写成 func f(label name: Type);想省略标签,用下划线 _ 显式省略。
| 写法 | 定义 | 调用 | 说明 |
|---|---|---|---|
| 省略标签 | func greet(_ site: String) | greet("RUNOOB") | 用 _ 显式省略外部标签 |
| 默认标签 | func greet(site: String) | greet(site: "RUNOOB") | 参数名同时作为局部名和外部标签 |
| 自定义标签 | func greet(to site: String) | greet(to: "RUNOOB") | 外部用 to,内部用 site |
| 后续参数省略标签 | func sum(_ a: Int, _ b: Int) | sum(2, 3) | 第二个及之后的参数同样可以用 _ 省略 |
下面的实例把三种写法放在一起对比。
实例
struct Runoob {
// 1. 用 _ 显式省略外部标签
func greet(_ site: String) -> String {
return "欢迎来到 \(site)"
}
// 2. 显式给出外部标签:label name
func visit(to site: String, times count: Int) -> String {
return "访问 \(site) \(count) 次"
}
// 3. 第二个参数也用 _ 省略外部标签
func sum(_ a: Int, _ b: Int) -> Int {
return a + b
}
}
let r = Runoob()
print(r.greet("RUNOOB"))
print(r.visit(to: "www.runoob.com", times: 3))
print(r.sum(2, 3))
运行结果:
欢迎来到 RUNOOB 访问 www.runoob.com 3 次 5
注意:Swift 3 起首参数默认就带外部标签,
func greet(site: String)必须用greet(site: "RUNOOB")调用。想省略标签,要在声明里显式写_:func greet(_ site: String)才能写成greet("RUNOOB")。「首参数默认没有外部标签」是 Swift 2 的旧规则,早已作废。
self 属性
每个实例都有一个隐含属性 self,它完全等同于实例本身。
当参数名与属性名相同时,用 self 明确指代属性,避免把赋值写成参数自己赋给自己。
实例
struct Point {
var x = 0.0, y = 0.0
// self 用于区分同名的属性与参数
init(x: Double, y: Double) {
self.x = x
self.y = y
}
func isToTheRightOf(_ other: Point) -> Bool {
return self.x > other.x
}
}
let a = Point(x: 10.0, y: 10.0)
let b = Point(x: 5.0, y: 5.0)
print("a 在 b 的右侧:\(a.isToTheRightOf(b))")
print("b 在 a 的右侧:\(b.isToTheRightOf(a))")
运行结果:
a 在 b 的右侧:true b 在 a 的右侧:false
实例方法里访问自身属性时,通常可以省略 self,Swift 会自动推断;只有在需要消除歧义时才必须写出来。
mutating 方法与 self 赋值
结构体和枚举是值类型,默认情况下实例方法不能修改自身的属性。
要让方法能修改属性,需要在 func 前加 mutating 关键字。
mutating 方法还有一个更强能力:可以把整个 self 替换成一个全新的实例。
实例
struct Counter {
var count = 0
mutating func increment() {
count += 1
}
// 用 self 把整个实例替换成一个新实例
mutating func resetToZero() {
self = Counter()
}
// 也可以替换成属性不同的新实例
mutating func doubled() {
self = Counter(count: count * 2)
}
}
var c = Counter()
c.increment()
c.increment()
print("递增后:\(c.count)")
c.doubled()
print("翻倍后:\(c.count)")
c.resetToZero()
print("重置后:\(c.count)")
运行结果:
递增后:2 翻倍后:4 重置后:0
注意:mutating 方法只能用
var声明的实例调用。用let声明的结构体实例,即使属性是变量也不能调用 mutating 方法。
类型方法
类型方法属于类型本身,不需要创建实例就能调用,用点号语法访问。
结构体和枚举的类型方法用 static 声明;类中可以用 class 声明可被子类重写的类型方法。
实例
struct MathHelper {
// 结构体的类型方法必须用 static
static func abs(_ number: Int) -> Int {
return number < 0 ? -number : number
}
}
class Shape {
var name: String
init(name: String) {
self.name = name
}
// class 修饰的类型方法允许子类重写
class func description() -> String {
return "这是一个形状"
}
static func category() -> String {
return "二维图形"
}
}
class Circle: Shape {
override class func description() -> String {
return "这是一个圆"
}
}
print(MathHelper.abs(-35))
print(Shape.description())
print(Circle.description())
print(Shape.category())
运行结果:
35 这是一个形状 这是一个圆 二维图形
区分两者的关键:static 定义的类型成员不能被重写,class 只能用于类,且允许子类用 override 重写。
注意:存储型的类型属性必须用
static声明,class只用于可重写的计算型类型属性或类型方法。
@discardableResult
如果方法的返回值在调用时经常被忽略,可以加上 @discardableResult,这样忽略返回值时编译器不会给出警告。
实例
struct BankAccount {
var balance: Int
// 返回值被忽略时不产生警告
@discardableResult
mutating func deposit(_ amount: Int) -> Int {
balance += amount
return balance
}
// 没有 @discardableResult,忽略返回值时编译器会给出警告
mutating func withdraw(_ amount: Int) -> Int {
balance -= amount
return balance
}
}
var account = BankAccount(balance: 100)
account.deposit(50)
let left = account.withdraw(30)
print("余额:\(left)")
运行结果:
余额:120
这个标记只是关闭警告,并不会改变方法的实际行为,返回值依然可以正常使用。
常见问题
下面几个问题在写方法时出现频率最高。
调用时提示 argument labels 不匹配
多半是标签写错了。先看定义里参数有没有外部标签:有 _ 就不写,写成 label name 就写 label,什么都不写就写参数名本身。
结构体方法里改属性报错
实例方法默认不能修改值类型的属性,给方法加上 mutating 即可。如果实例是用 let 声明的,还要改成 var。
类型方法里访问实例属性报错
类型方法没有实例上下文,不能直接访问实例属性。要么把方法改成实例方法,要么把数据也做成类型属性。
