现在位置: 首页 > Swift 教程 > 正文

Swift 方法

方法是与某个特定类型(类、结构体或枚举)关联的函数,用来描述这个类型能做什么。

它与普通函数最大的区别是:方法要通过某个实例或类型来调用,并且可以直接访问该实例的属性。


实例方法

实例方法属于某个具体实例,可以读取和修改该实例的属性,也可以调用同类型的其他方法。

下面这个 Counter 类用三个实例方法管理一个计数器。

实例

import Foundation

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)第二个及之后的参数同样可以用 _ 省略

下面的实例把三种写法放在一起对比。

实例

import Foundation

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 明确指代属性,避免把赋值写成参数自己赋给自己。

实例

import Foundation

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 替换成一个全新的实例。

实例

import Foundation

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 声明可被子类重写的类型方法。

实例

import Foundation

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,这样忽略返回值时编译器不会给出警告。

实例

import Foundation

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

类型方法里访问实例属性报错

类型方法没有实例上下文,不能直接访问实例属性。要么把方法改成实例方法,要么把数据也做成类型属性。