Swift init 方法
我们注意到在 Objective-C 中,init方法是非常不安全的。与之相对, Swift对初始化方法有了相当严格的定义:
-
在父类中,designated init 方法需要在方法中保证全部非 Optional 的实例变量被赋值/初始化,
-
在子类中, 各种显示写出的init方法也也强制调用super版本的designated init方法,无论如何走何种路径,被初始化的对象总是可以完成完整的初始化的
- convenicence init的实现中, 仍需调用 子类的 designated init 方法(有时被标记成required)
- override designated init的实现中, 仍需调用 super designated init 方法
-
在子类 init 方法实现的调用顺序上,Swift依然进行了严格的规定:先调用子类实例变量的赋值初始化, 然后调用父类designated init方法。
Swift init 做了啥
我们知道一般而言对class instance的初始化至少要申请堆内存空间, 标定type类型,并绑定一定的内存偏移到instance variable。 正在下面的例子中,我们注意到父类self和子类self的访问时机,父类和子类在没写任何东西的情况下扔然可以调用self.
class ParentClass {
var name: String
init() {
// 此时self可以访问到,可以推测父类类内存申请/类型绑定完毕
self.name = ""
}
}
class ChildClass: ParentClass {
var number: Int
override init() {
// 此时self可以访问到,可以推测子类内存申请/类型绑定完毕
self.number = 0
super.init()
}
}
let p = ParentClass()
let c = ChildClass()
通过阅读Swift的编译器说明,我们可以了解到过程是Source -> AST -> SIL -> machine code。相应的我们可以假设class instance 的初始化过程中,某些方法的调用被省略/隐藏,或者通过其init方法的声明,进行了隐藏的方法调用。
这里有两个问题可以确认 AST部分应该仅反映源代码的语法,还原语法糖,不会附带隐藏的方法。而SIL更接近一个平台独立的machine code伪代码。 AST部分的解析可以参照 swiftSyntax 和这个AST查看器:
- github.com/apple/swift…
- swift-ast-explorer.com/ SIL部分的解释更多的来自这里:
- github.com/apple/swift…
那么下面的目标,我们尝试在SIL里查看源码有哪些变化
Swift SIL 做了啥
以下两个命令我们可能用到,可附加在building phase里,来得到SIL输出
生成SIL,优化前 swiftc -emit-silgen [source filepath] > ./[sil filename.sil]
生成SIL,优化后 swiftc -emit-sil [source filepath] > ./[sil filename.sil]
对于优化后的SIL,我们可以得到等效于隐式main函数的SIL代码:
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main1pAA11ParentClassCvp // id: %2
%3 = global_addr @$s4main1pAA11ParentClassCvp : $*ParentClass // user: %7
%4 = metatype $@thick ParentClass.Type // user: %6
// function_ref ParentClass.__allocating_init()
%5 = function_ref @$s4main11ParentClassCACycfC : $@convention(method) (@thick ParentClass.Type) -> @owned ParentClass // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick ParentClass.Type) -> @owned ParentClass // user: %7
store %6 to %3 : $*ParentClass // id: %7
alloc_global @$s4main1cAA10ChildClassCvp // id: %8
%9 = global_addr @$s4main1cAA10ChildClassCvp : $*ChildClass // user: %13
%10 = metatype $@thick ChildClass.Type // user: %12
// function_ref ChildClass.__allocating_init()
%11 = function_ref @$s4main10ChildClassCACycfC : $@convention(method) (@thick ChildClass.Type) -> @owned ChildClass // user: %12
%12 = apply %11(%10) : $@convention(method) (@thick ChildClass.Type) -> @owned ChildClass // user: %13
store %12 to %9 : $*ChildClass // id: %13
%14 = integer_literal $Builtin.Int32, 0 // user: %15
%15 = struct $Int32 (%14 : $Builtin.Int32) // user: %16
return %15 : $Int32 // id: %16
} // end sil function 'main'
这里我们注意到%4 %5 %6和%10 %11 %12,这俩分别是我们初始化 ParentClass 和 ChildClass 的地方。
接着,我们找到函数 ParentClass.__allocating_init()对应的SIL代码如下。注意这里的 %1 alloc_ref,根据SIL文档我们得出,%1实际上allocate 了 ParentClass。那么此时发生在我们调用ParentClass.init之前,这似乎符合我们的假设,这里就是隐藏的调用。
// ParentClass.__allocating_init()
sil hidden [exact_self_class] @$s4main11ParentClassCACycfC : $@convention(method) (@thick ParentClass.Type) -> @owned ParentClass {
// %0 "$metatype"
bb0(%0 : $@thick ParentClass.Type):
%1 = alloc_ref $ParentClass // user: %3
// function_ref ParentClass.init()
%2 = function_ref @$s4main11ParentClassCACycfc : $@convention(method) (@owned ParentClass) -> @owned ParentClass // user: %3
%3 = apply %2(%1) : $@convention(method) (@owned ParentClass) -> @owned ParentClass // user: %4
return %3 : $ParentClass // id: %4
} // end sil function '$s4main11ParentClassCACycfC'
至此,我们知道了表层上ParentClass.__allocating_init()这个是实际的初始化调用,而且在ParentClass.init()之前还有一个调用将ParentClass的内存申请完毕。
SIL alloc_ref 做了啥
下面我们尝试找到ParentClass.__allocating_init() alloc_ref这一步调用的符号,来定位在swift源码中的调用
注意这里的L9, swift_allocObject紧接着ParentClass.init之前的调用, 定位到swift源码里最终调用了malloc:
HeapObject.cpp --> swift_allocObject --> _swift_allocObject_ --> swift_slowAlloc
Heap.capp --> swift_slowAlloc --> malloc_zone_malloc/malloc
这里可以注意到ParentClass instance是由HeapObject返回的。而 HeapMetadata 作为parameter含有ParentClass的内存空间描述的全部信息,这里可以尝试将 HeapMetadata 这部分内存块被swift struct类型重新绑定: HeapMetadata 的定义可以找到:TargetHeapMetadata --> TargetMetadata --> getTypeContextDescriptor --> TargetClassMetadata