【二】Swift-类与结构体(下)
........................................................
目录:
-
一、异变方法;
a.使用SIL探查mutating原理 - 二、方法调度;
- 三、影响函数派发方式;
- 三、函数内联;
........................................................
一、异变方法;
'self' is immutable.png上一篇已经讲过:Swift中class和struct都能定义方法。但有一点区别的是默认情况下,值类型属性不能被自身的实例方法修改。
当前结构体中的self
里存放的就是属性x和y的值。
虽然我们在moveBy中没有使用self.x、self.y
方法,
但是当我们修改x
或者y
的时候,就相当于修改self本身
。
由于默认情况下,值类型属性不能被自身的实例方法修改。
所以系统编译的时候会爆红警告。
//比如我们实例化一个对象p,调用moveBy方法的时候:
var p = Point()
p.moveBy(x: 8, y: 13)
//p修改x、y的时候,内部函数的修改,也修改了外部的p自身。
那么我们如果想要在结构体内部的函数中修改自身的值,就要在方法前边加上关键词mutating
修饰。
a.使用SIL探查mutating具体原理
SIL的具体使用方法已经在上一篇:Swift-类与结构体(下)中有详细的介绍。
//示例代码:
struct YGPoint {
var x = 0.0, y = 0.0;
//普通方法
func test() {
let tmp = self.x
print(tmp)
}
//使用了mutating修饰的方法
mutating func moveBy(x deltaX: Double , y deltaY: Double) {
x += deltaX
y += deltaY
}
}
使用如下命令
swiftc -emit-sil ${SRCROOT}/xiaomaTest/main.swift > ./main.sil && open -a 'Visual Studio Code' 'main.sil'
xiaomaTest
是项目根目录下的文件名,
Visual Studio Code
是用来打开main.sil文件的工具,这里也可以用Xcode等开发工具。
- 得到的关键SIL文件信息如下:
struct YGPoint {
@_hasStorage @_hasInitialValue var x: Double { get set }
@_hasStorage @_hasInitialValue var y: Double { get set }
func test()
mutating func moveBy(x deltaX: Double, y deltaY: Double)
init()
init(x: Double = 0.0, y: Double = 0.0)
}
/***************test()方法******************/
// YGPoint.test()
sil hidden @$s4main7YGPointV4testyyF : $@convention(method) (YGPoint) -> () {
// %0 "self" // users: %2, %1
bb0(%0 : $YGPoint):
debug_value %0 : $YGPoint, let, name "self", argno 1 // id: %1
%2 = struct_extract %0 : $YGPoint, #YGPoint.x // users: %11, %3
...
/*************** moveBy()方法******************/
// YGPoint.moveBy(x:y:)
sil hidden @$s4main7YGPointV6moveBy1x1yySd_SdtF : $@convention(method) (Double, Double, @inout YGPoint) -> () {
// %0 "deltaX" // users: %10, %3
// %1 "deltaY" // users: %20, %4
// %2 "self" // users: %16, %6, %5
bb0(%0 : $Double, %1 : $Double, %2 : $*YGPoint):
debug_value %0 : $Double, let, name "deltaX", argno 1 // id: %3
debug_value %1 : $Double, let, name "deltaY", argno 2 // id: %4
debug_value_addr %2 : $*YGPoint, var, name "self", argno 3 // id: %5
%6 = begin_access [modify] [static] %2 : $*YGPoint // users: %15, %7
...
- 通过以上文件我们可以看到:
test()
方法的 名称混编后变成s4main5PointV4testyyF
,
返回值是@convention(method)
,
test()方法的self
参数对应的是Point
。
moveBy(x:y:)
方法的 名称混编后变成s4main5PointV6moveBy1x1yySd_SdtF
,
返回值是@convention(method)
,
moveBy(x:y:)方法的参数x、y、self
参数对应的是Double, Double, @inout Point
。
@inout 输入输出参数
不同的地方就是:第二个方法的self参数多了一个
@inout
参数,
SIL文档中介绍《SIL基本语法介绍》
An@inout
parameter is indirect. The address must be of an initialized object. (当前参数类型是间接的,传递的是已经初始化的地址
)
也就是说,moveBy方法中的 @inout Point接收的是一个地址,
test()方法中的Point接收的是一个结构体的实例(self)也就是结构体具体的值
//Test()方法的第一行SIL代码
debug_value %0 : $Point, let, name "self", argno 1 // id: %1
//翻译过来相当于:
let self = Point
//moveBy(x:y:)方法的第三行SIL代码
debug_value_addr %2 : $*Point, var, name "self", argno 3 // id: %5
//翻译过来相当于:
var self = *Point
- 使用swift代码解释
var p = YGPoint()
//取值 -- 相当于没有加mutating的方法
let te1 = p
//取地址相当于mutating修饰后的方法,有了@inout修饰
var te2 = withUnsafePointer(to: &p){return $0}
//取值
var te3 = p
//给P的x赋值,查看te1、te2、te3的x变化
p.x = 30.0
print("te1=",te1.x,"\nte2=",te2.pointee.x,"\nte3=",te3.x)
输出结果如下图所示:
1421641377880_.pic.jpg
我们可以看出修改实例变量p
的里的x值,只有te2
跟着变化了。
所以没有添加mutating的方法相当于取值,而
mutating
修饰的方法相当于取地址
。(@inout
关键词的作用就是传入当前的地址)所以此时我们修改self是合法的。