「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战」
类与结构体的方法
我们知道,不管是Class还是Struct都能够自定义方法;那么Class和Struct定义的方法有没有什么区别呢?
我们分别定义一个Class和Struct,然后给他们添加同样的方法,代码如下:
同样的方法操作,在Class中可以,在Struct中就会报错,这是因为值类型属性不能被自身的实例方法修改 ;
根据报错信息我可以知道:Struct结构体中x和y也就是结构体本身self不能在move方法中进行修改,这个时候如果我们修复这个错误时,代码会自动变成:
方法前被添加了
mutating标识;
那么mutating对方法进行了什么操作呢?
mutating关键字
我们在PointA中添加一个方法test,代码如下:
我们通过命令swiftc main.swift -emit-sil > ./main.sil来生成SIL文件来分析,这两个方法有什么不同,我们先来看两个方法的声明:
在声明部分,两个方法除了mutating的区别之外没有什么区别,那么两个函数的实现呢?先来看test函数在SIL中的实现:
根据上述实现代码我们可以分析出:test函数是有一个默认参数PointA也就是self的;这跟我们OC中类似,在OC中方法是有两个默认参数self和cmd的;在test函数的实现中,我们需要注意这样一行代码:
debug_value %0 : $PointA, let, name "self", argno 1 // id: %1
接下来,我们看一下move函数的实现:
我们注意到,move函数除了两个Double类型的参数之后,也多了一个PointA,不同的是,PointA这个默认参数被添加上了@inout标识;除此之外,我们还需要下边代码:
debug_value_addr %2 : $*PointA, var, name "self", argno 3 // id: %5
我们将其与test方法中的代码进行比较:
// test函数
sil hidden @$s4main6PointAV4testyyF : $@convention(method) (PointA) -> ()
debug_value %0 : $PointA, let, name "self", argno 1 // id: %1
// move函数
sil hidden @$s4main6PointAV4move6orginX0D1YySd_SdtF : $@convention(method) (Double, Double, @inout PointA) -> ()
debug_value_addr %2 : $*PointA, var, name "self", argno 3 // id: %5
move函数的默认参数PointA比test函数的默认参数多了@inout标识;
根据SIL官方文档关于@inout的解释是:
An @inout parameter is indirect. The address must be of an initialized object. The memory must remain initialized for the duration of the call until the function returns.
意为:@inout的参数是间接的。地址必须是初始化对象的地址。在调用期间,内存必须保持初始化状态,直到函数返回。
test函数中传递的默认参数PointA是一个结构体的值,而move函数的默认参数PointA因为标识为@inout,其传递的其实是地址;
- 从
debug_value代码也能分析出,在test函数中的self指的是PointA的值,其被let修饰,因此是无法修改的;而move函数中的self指的是PointA的地址,被var修饰,可以进行修改;
我们可以通过如下代码进行验证:
通过指针地址获取的对象属性被修改了
使用了mutating关键字的方法我们称为异变方法,对于此类方法,传入的self会被标记inout,无论在mutating方法内部发生什么,都会影响外部依赖类型的一切;
inout称为输入输出参数;一般函数的参数都是形式参数,都是let类型的,无法被修改,而使用了inout的参数可以被修改;
如下代码所示:
我们想通过修改v的值,达到修改value的目的,但是因为v是一个形式参数,其是let类型的,因此无法被修改;
接下来我们将v修改为inout参数:
因为v使用了inout,因此传参时需要传递地址&value,在内部修改v的值之后,外部的value值也发生了改变;