Swift方法mutating关键字的本质

Swift结构体或者枚举的方法中,如果方法中需要修改当前结构体或者枚举属性值,则需要再func前面加上mutating关键字,否则编译器会直接报错。

✅ 方法中修改属性必须加上mutating

struct Point {
    var x: Int
    mutating func setX(_ value: Int) {
        self.x = value
    }
}

❌ 不加报错

存储属性修改

❌ 不加报错


计算属性修改

接下来我们就来看看mutating关键字的底层实现逻辑到底是什么?

汇编分析

不加mutating关键字的setX方法:
struct Point {
    var x: Int
    func setX(_ value: Int) {
        let _ = self.x
    }
}

测试代码如下:

<!-- 代码 -->
func test() {
    var p = Point(x: 1)
    p.setX(2)
}

<!-- 汇编 -->
JJSwift`test():
    0x100003ed8 <+0>:  sub    sp, sp, #0x20            // 压栈32个字节
    0x100003edc <+4>:  stp    x29, x30, [sp, #0x10]
    0x100003ee0 <+8>:  add    x29, sp, #0x10          
    0x100003ee4 <+12>: str    xzr, [sp, #0x8]          // sp+0x8的内存地址清零 
    0x100003ee8 <+16>: mov    w8, #0x1                 
    0x100003eec <+20>: mov    x0, x8                   // 将1赋值给x0寄存器
->  0x100003ef0 <+24>: bl     0x100003ed4              // 结构体初始化
    0x100003ef4 <+28>: mov    x1, x0                   // 将1赋值给x1寄存器作为参数          
    0x100003ef8 <+32>: str    x1, [sp, #0x8]           // 将1赋值给p对象
    0x100003efc <+36>: mov    w8, #0x2                 
    0x100003f00 <+40>: mov    x0, x8                   // 将2存储在x0寄存器上,作为参数
    0x100003f04 <+44>: bl     0x100003eb8              // 调用JJSwift.Point.setX(Swift.Int)方法
    0x100003f08 <+48>: ldp    x29, x30, [sp, #0x10]
    0x100003f0c <+52>: add    sp, sp, #0x20            // 出栈
    0x100003f10 <+56>: ret                             // 返回    

Point.setX(Swift.Int)有两个参数,参数1(x0寄存器)2参数2(x1寄存器):p对象的值1
test()方法的函数栈占用32个字节

mutating关键字的setX方法:
struct Point {
    var x: Int
    mutating func setX(_ value: Int) {
        let _ = self.x
    }
}

测试代码如下:

<!-- 代码 -->
func test() {
    var p = Point(x: 1)
    p.setX(2)
}

<!-- 汇编 -->
JJSwift`test():
    0x100003ecc <+0>:  sub    sp, sp, #0x30             // 压栈48个字节
    0x100003ed0 <+4>:  stp    x20, x19, [sp, #0x10]
    0x100003ed4 <+8>:  stp    x29, x30, [sp, #0x20]
    0x100003ed8 <+12>: add    x29, sp, #0x20            
    0x100003edc <+16>: add    x20, sp, #0x8             // 保存sp+0x8地址到x20寄存器上
    0x100003ee0 <+20>: str    xzr, [sp, #0x8]           // sp+0x8地址清零
    0x100003ee4 <+24>: mov    w8, #0x1              
    0x100003ee8 <+28>: mov    x0, x8                    // 1放在x0寄存器上
->  0x100003eec <+32>: bl     0x100003ec8               // 结构体初始化
    0x100003ef0 <+36>: str    x0, [sp, #0x8]            // 将p对象放在sp+0x8地址
    0x100003ef4 <+40>: mov    w8, #0x2
    0x100003ef8 <+44>: mov    x0, x8                    // 2 放在x0寄存器上 作为参数
    0x100003efc <+48>: bl     0x100003eac               // 调用Point.setX(Swift.Int) -> ()
    0x100003f00 <+52>: ldp    x29, x30, [sp, #0x20]
    0x100003f04 <+56>: ldp    x20, x19, [sp, #0x10]
    0x100003f08 <+60>: add    sp, sp, #0x30             // 出栈
    0x100003f0c <+64>: ret                              // 返回    

Point.setX(Swift.Int)有两个参数,参数1(x0寄存器)2参数2(x20寄存器):p对象的存储地址sp+0x8
test()方法的函数栈占用48个字节

setX方法修改属性值
struct Point {
    var x: Int
    mutating func setX(_ value: Int) {
        self.x = value
    }
}

func test() {
    var p = Point(x: 1)
    p.setX(2)
}

我们进入setX看看实现逻辑:

<!-- 代码 -->
p.setX(2)


<!-- 汇编 -->
JJSwift`Point.setX(_:):
->  0x100003ea8 <+0>:  sub    sp, sp, #0x10            
    0x100003eac <+4>:  str    xzr, [sp, #0x8]
    0x100003eb0 <+8>:  str    xzr, [sp]       
    0x100003eb4 <+12>: str    x0, [sp, #0x8]  
    0x100003eb8 <+16>: str    x20, [sp]        
    0x100003ebc <+20>: str    x0, [x20]       // 内存地址直接修改为新值
    0x100003ec0 <+24>: add    sp, sp, #0x10           
    0x100003ec4 <+28>: ret    

setX中会对内存地址(x20寄存器中的值)直接修改成新值(x0寄存器中的值),也就是直接在传入的内存地址上直接修改

结论
  • 普通函数传值参数是值传递,加mutating关键字后参数会变成地址传递;

SIL分析

函数的参数传递的是地址,这是不是很容易让人联想到mutating关键字是不是就是利用的inout关键字呢?

我们就利用中间代码来看下:

不加mutating关键字的setX方法:
struct Point {
    var x: Int
    func setX(_ value: Int) {
        let _ = self.x
    }
}
// Point.setX(_:)
sil hidden @$s4main5PointV4setXyySiF : $@convention(method) (Int, Point) -> () {
// %0 "value"                                     // user: %2
// %1 "self"                                      // users: %4, %3
bb0(%0 : $Int, %1 : $Point):
  %4 = struct_extract %1 : $Point, #Point.x       // 获取point的值
  %5 = tuple ()                                   // user: %6
  return %5 : $()                                 // id: %6
}
mutating关键字的setX方法:
struct Point {
    var x: Int
    func setX(_ value: Int) {
        let _ = self.x
    }
}
// Point.setX(_:)
sil hidden @$s4main5PointV4setXyySiF : $@convention(method) (Int, @inout Point) -> () {
// %0 "value"                                     // user: %2
// %1 "self"                                      // users: %4, %3
bb0(%0 : $Int, %1 : $*Point):
  %4 = begin_access [read] [static] %1 : $*Point  // 获取point内存地址
  %5 = struct_element_addr %4 : $*Point, #Point.x
  end_access %4 : $*Point                         // id: %6
  %7 = tuple ()                                   // user: %8
  return %7 : $()                                 // id: %8
}
结论
  • mutating关键字后,第二个参数确实变成了@inout参数
  • @inout修饰的参数是地址传递,所以符合汇编结果。

总结

mutating关键字本质是包装了inout关键字,加上mutating关键字后参数值会变成地址传递。
类对象是指针,传递的本身就是地址值,所以 mutating关键字对类是透明的,加不加效果都一样。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容