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
之后,底层默认传入了一个标记了 inout
的 Self
参数进来,这样 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的呢?反而需要开发者明确标明?"我没有找到官方对此的明确说明,猜测可能有一下几点
- 使代码意图更加明确,明确表示这是一个会改变状态的操作
- 关于函数式编程的思考,swift 是函数式编程语言,可以说struct从某种程度上体现了函数式编程的优势(比如它的赋值会导致copy),swift建议的是struct中一个方法对当前的数据进行处理修改后,返回一个新的struct变量,而非直接对当前内存进行修改
- 并发安全性的保证,mutating可以明确表示这是一个可能影响线程安全的操作
写文章不易,如果你觉的有收获,点个在看支持一下~ 看下我的其他文章