swift为什么会需要mutating关键字

Swift 帮助我们编写更健壮的代码的方式之一是通过其值类型 的概念,它限制了状态可以跨 API 边界共享的方式。mutating除了修改属性,还可以给self 赋予一个全新的值, 例如以下代码中的change方法

swift 复制代码
struct Point {   
    var x: Int    
    var y: Int      
    mutating func addOne(){        
        x = x + 1        
        y = y + 1    
    }       
    mutating func change(){        
        self = Point(x: 100, y: 100)    
    }
}

注意struct中如果有引用类型,修改其中引用类型的属性是不需要mutating的

swift 复制代码
struct Point {    
    var x: Int    
    var y: Int    
    var cat: Cat        
    func setAgeOfCat(_ ageNum: Int) {        
    self.cat.age = ageNum //这里不需要mutating也可以正常赋值    
    }
} 

struct中有另外一个struct,又表现如何?

swift 复制代码
struct Size {    
    var x: Int    
    var y: Int    
    mutating func change(){        
        x+=1    
    }        
    func perimeter() -> Int{        
        x + y    
    }
}
struct Point {    
    var x: Int    
    var y: Int    
    var p: Size      
    mutating func addOne(){        
        x = x + 1        
        y = y + 1    
    }        
    func change(){        
        self.p.change() //报错Cannot use mutating member on immutable value: 'self' is immutable    
    }
}

那么我们不禁要问自己let 的管辖范围究竟如何答案是let 对所有值类型都能保证不可变(包括数组、字典、字符串等),对引用类型只保证引用不可变。因为let保证的是变量地址中的值不能改变,我想其在修饰struct时也会递归的将其内部的变量设置为let,而let修饰的struct是不能调用其任何mutating方法的,这个编译器也会检查

swift 复制代码
func getName(isMan: Bool) -> String {    
let name: String
    if isMan {        
        name = "season"    
    }else {        
        name = "soso"    
    }    
    return name
}
let aName = getName(isMan: true)
print(aName)

这段是没有问题的,思考一下是为什么?Mutating方法的工作原理到底是什么?复制还是inout?既然可变struct变量(var)在外部被使用时可以被修改属性,为什么struct内部修改属性却需要mutating?

有一些文章中是这样写的,但是注意这是错误的:

当调用一个mutating方法时,Swift会隐式地将当前实例复制一份,并在复制的实例上进行修改。这意味着,调用mutating方法后,原始实例的值会发生变化,但实际上是操作了一个副本。避免不必要的复制 : 在某些情况下,可以通过设计避免使用mutating方法,从而减少不必要的内存复制可以使用inout参数来避免使用mutating方法,从而实现更高效的内存操作。看了很多这方面的分享,很多还是需要自己用代码去验证为了回答以上问题,我们先验证一些表现:

swift 复制代码
struct Point {    
    var x: Int    
    var y: Int        
    func movedBy(x: Int, y: Int) -> Point {        
    return Point(x: self.x + x, y: self.y + y)   
    }    
//    private func move(x1: Int, y1: Int){
//        x = x + x1 //Cannot assign to property: 'self' is immutable
//        y = y + y1 //Cannot assign to property: 'self' is immutable
//    }    
}
class ViewController: UIViewController {    
    override func viewDidLoad() {        
        testChange()    
    }        
    private func testChange(){        
        var point = Point(x: 1, y: 1)        
        point.x += 1 //不会有错误    
    }
}

这里我们注意到提示的是

csharp 复制代码
'self' is immutable

这就类似于

less 复制代码
let point = Point(x: 1, y: 1,cat: Cat())
point.x += 1  // 编译错误Left side of mutating operator isn't mutable: 'point' is a 'let' constant

我们来看这段代码中mutating的方法和非mutating方法在编译为sil后都有什么不同

swift 复制代码
struct Point {    
    var x: Int    
    var y: Int        
    mutating func XAddOne(){        
        x = x + 1    
    }        
    func printX() -> Int{        
        return x + y    
    }    
}

其编译结果为(其他非重要部分省略)由红框和红线部分的代码即可知道其中真义添加 mutating 之后,底层默认传入了一个标记了 inoutSelf 参数进来,这样 self 就可以修改了还有一种说法:mutating 关键字存在的原因在于结构体和枚举是值类型。在 Swift 中,当你改变一个值类型的属性时,本质上是在改变值的副本但是这完全是错误的,我们来验证一下

less 复制代码
struct Point {    
    var x: Int    
    var y: Int      
    mutating func addOne(){        
        x = x + 1        
        y = y + 1    
    }
}
var  point = Point(x: 1, y: 1,cat: Cat())//在此处查看内存
point.addOne()//在此处再次查看内存

其结果为

ini 复制代码
(lldb) frame variable -L point
0x000000016ce0f6c0: (sort.Point) point = {
0x000000016ce0f6c0:   x = 1
0x000000016ce0f6c8:   y = 1
scalar:   cat = 0x000060000022a160 {
    0x000060000022a170:     age = 0  
    }
}
(lldb) frame variable -L point
0x000000016ce0f6c0: (sort.Point) point = {
0x000000016ce0f6c0:   x = 2
0x000000016ce0f6c8:   y = 2
scalar:   cat = 0x000060000022a160 {
0x000060000022a170:     age = 0  
    }
}

可以看出不但point的地址没有变化,其中的x和y的地址也没有变化,这里只是对内存中的内容进行更新而已!!!

到这里我们标题中的问题演化为"为什么struct中的方法不默认就是mutating的呢?反而需要开发者明确标明?"我没有找到官方对此的明确说明,猜测可能有一下几点

  1. 使代码意图更加明确,明确表示这是一个会改变状态的操作
  2. 关于函数式编程的思考,swift 是函数式编程语言,可以说struct从某种程度上体现了函数式编程的优势(比如它的赋值会导致copy),swift建议的是struct中一个方法对当前的数据进行处理修改后,返回一个新的struct变量,而非直接对当前内存进行修改
  3. 并发安全性的保证,mutating可以明确表示这是一个可能影响线程安全的操作

写文章不易,如果你觉的有收获,点个在看支持一下~ 看下我的其他文章

相关推荐
大熊猫侯佩19 小时前
SwiftUI 6.0(iOS 18)自定义容器值(Container Values)让容器布局渐入佳境(下)
swiftui·swift·apple
大熊猫侯佩21 小时前
用接地气的例子趣谈 WWDC 24 全新的 Swift Testing 入门(二)
单元测试·swift·apple
我现在不喜欢coding21 小时前
swift中的self,Self,Class(struct).Type让你头大了嘛?
ios·swift
不二狗21 小时前
每日算法 -【Swift 算法】查找字符串数组中的最长公共前缀
开发语言·算法·swift
不二狗21 小时前
每日算法 -【Swift 算法】将整数转换为罗马数字
开发语言·算法·swift
InternLM1 天前
论文分类打榜赛Baseline:ms-swift微调InternLM实践
swift·internlm·书生
YungFan2 天前
SwiftUI-Markdown渲染
swiftui·swift