5. 异变方法-mutating

254 阅读2分钟

一、异变方法

我们知道,不管是Class还是Struct都能够定义方法,那么它们有区别吗? 分别定义一个ClassStruct,并给它们添加相同的方法,代码如下:

struct Person {
    var age = 3

    func modify(age: Int) {
        self.age = age
    }
}

class Teacher {
    var age = 3

    func modify(age: Int) {
        self.age = age
    }
}

编译时会发现Struct中的modify方法会报错,而class中并没有,这是因为值类型的属性不能被自身的实例方法修改。根据提示自动修复后即可编译成功,像这样: c9024d159d9bc03d096d5fef5c99df42.png 很明显,modify方法前多了一个关键字mutating,那么它的作用是什么?

二、mutating的作用

  1. 为了方便对比,我们继续在Struct中添加一个test方法
struct Person {
    var age = 3

    mutating func modify(age: Int) {
        self.age = age
    }
    
    func test() {
        print(age)
    }
}
  1. 分析SIL文件 cc3c245fd337ee31e38bfbe02ea98910.png 从声明来看modify方法多了一个关键字mutating

  2. 分析test方法的实现 5c1f5aa3bcec2ea1634c0af2e4ee914b.png 从这里可以看出test方法携带一个入参Person,该入参就是Person实例的值。 注意debug_value %0 : $Person, let, name "self", argno 1是声明一个常量self存贮Person实例。

  3. 分析modify方法的实现 f843e10a4a377455a634f245fb95b310.pngtest相比,modify入参为@inout Person,该入参其实是Person实例的地址。 再看debug_value_addr %0 : $*Person, var, name "self", argno 1是在声明变量self存储Person实例的地址。从这不难看出modify是在对实例地址进行操作,而test仅仅是取值。

SIL 文档的解释 An @inout parameter is indirect. The address must be of an initialized object.(当前参数 类型是间接的,传递的是已经初始化过的地址)

三、演示代码

struct Person {
    var age = 3
}

var p = Person()
let p1 = p
let p2 = withUnsafePointer(to: &p) { $0 }
p.age = 10
print("p1.age = ", p1.age)
print("p2.age = ", p2.pointee.age)

执行结果

p1.age =  3
p2.age =  10
Program ended with exit code: 0

由此可以看出,修改p的属性时,通过取地址的p2也被修改了,而直接取值的p1并不受影响。

四、总结

使用关键字mutating的方法称为异变方法,该方法会将入参中携带的当前实例self标记inout,后续该方法中任何对self的操作都是取self的地址进行操作,它会直接影响到外界依赖。

inoutswift中也是关键字,表示输入输出参数。该键字标记的参数可以在方法内部被修改(地址传递)。