Swift(五)-异变方法

205 阅读3分钟

「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战

类与结构体的方法

我们知道,不管是Class还是Struct都能够自定义方法;那么ClassStruct定义的方法有没有什么区别呢?

我们分别定义一个ClassStruct,然后给他们添加同样的方法,代码如下:

image.png

同样的方法操作,在Class中可以,在Struct中就会报错,这是因为值类型属性不能被自身的实例方法修改

根据报错信息我可以知道:Struct结构体中xy也就是结构体本身self不能在move方法中进行修改,这个时候如果我们修复这个错误时,代码会自动变成:

image.png

方法前被添加了mutating标识;

那么mutating方法进行了什么操作呢?

mutating关键字

我们在PointA中添加一个方法test,代码如下:

image.png

我们通过命令swiftc main.swift -emit-sil > ./main.sil来生成SIL文件来分析,这两个方法有什么不同,我们先来看两个方法的声明:

image.png

在声明部分,两个方法除了mutating的区别之外没有什么区别,那么两个函数的实现呢?先来看test函数在SIL中的实现:

image.png

根据上述实现代码我们可以分析出:test函数是有一个默认参数PointA也就是self的;这跟我们OC中类似,在OC中方法是有两个默认参数selfcmd的;在test函数的实现中,我们需要注意这样一行代码:

debug_value %0 : $PointA, let, name "self", argno 1 // id: %1

接下来,我们看一下move函数的实现:

image.png

我们注意到,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函数的默认参数PointAtest函数的默认参数多了@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修饰,可以进行修改;

我们可以通过如下代码进行验证:

image.png

通过指针地址获取的对象属性被修改了

使用了mutating关键字的方法我们称为异变方法,对于此类方法,传入的self会被标记inout,无论在mutating方法内部发生什么,都会影响外部依赖类型的一切;

inout称为输入输出参数;一般函数的参数都是形式参数,都是let类型的,无法被修改,而使用了inout的参数可以被修改;

如下代码所示:

image.png

我们想通过修改v的值,达到修改value的目的,但是因为v是一个形式参数,其是let类型的,因此无法被修改;

接下来我们将v修改为inout参数:

image.png

因为v使用了inout,因此传参时需要传递地址&value,在内部修改v的值之后,外部的value值也发生了改变;