结构体
struct Teacher{
var age:Int
func teach() {
print("teach")
}
}
var age = Teacher(age: <#Int#>)
我们声明一个结构体,如果没有初始化赋值,我们需要在创建的时候赋值.
查看SIL
struct Teacher {
@_hasStorage var age: Int { get set }
func teach()
init(age: Int)
}
系统会自动帮我们生成初始化方法,如果是class我们需要自己在init方法中初始化。
如果我们的属性有默认初始值,系统会提供不同的默认初始化方法
struct Teacher {
@_hasStorage @_hasInitialValue var age: Int { get set }
func teach()
init(age: Int = 18)
init()
}
如果我们自定义初始化方法,系统就不会帮我们生成初始化方法
结构体是值类型
我们首先看一个例子
func test(){
var age = 18
var age2 = age
age = 30
age2 = 45
print("age=\(age),age2=\(age2)")
}
通过lldb进行打印
(lldb) po withUnsafeMutablePointer(to: &age, {print($0)})
0x00007ffeefbff410
0 elements
这就是我们栈上的地址
x/4g 0x00007ffeefbff410
0x7ffeefbff410: 0x0000000000000012 0x0000000000000000
0x7ffeefbff420: 0x00007ffeefbff440 0x0000000100000c44
我们在打印age2的信息
(lldb) x/4g 0x00007ffeefbff408
0x7ffeefbff408: 0x0000000000000012 0x0000000000000012
0x7ffeefbff418: 0x0000000000000000 0x00007ffeefbff440
我们后面修改的就是独立地址的值
所以这就是个值类型
- 地址存储的就是值
- 传递的就是中就是个副本
struct Teacher{
var age:Int = 18
var age2:Int = 20
}
var t = Teacher()
················
(lldb) po t
▿ Teacher
- age : 18
- age2 : 20
在lldb中po打印的就直接是t的值
(lldb) po withUnsafeMutablePointer(to: &t, {print($0)})
0x0000000100002038
0 elements
(lldb) x/4g 0x0000000100002038
0x100002038: 0x0000000000000012 0x0000000000000014
0x100002048: 0x0000000000000000 0x0000000000000000
可以看到地址里存的直接是值。
通过SIL我们可以知道他没有alloc去堆上开辟空间。
我们再看看class
class Teacher1{
var age:Int = 18
var age2:Int = 20
}
var t1 = Teacher1()
············
(lldb) po t1
(lldb) x/4g 0x104100180
0x104100180: 0x0000000100003170 0x0000000600000002
0x104100190: 0x0000000000000012 0x0000000000000014
打印出kind, refCounts, age, age2
类是引用类型,当别的地方修改的时候,其他地方的引用也会被修改
class Teacher1{
var age:Int = 18
var age2:Int = 20
}
struct Teacher{
var age:Int = 18
var age2:Int = 20
var teacher = Teacher1()
}
var t = Teacher()
var t1 = t
t.teacher.age = 30;
print(t1.teacher.age)
···············
30
因此我们在结构体中属性是引用类型也会将地址复制过去。所以我们要避免这样的写法。
mutating
struct Stack{
var items = [Int]()
func push(item:Int) {
items.append(item)
}
}
在结构体里,我们直接向数组里添加是不允许的,会和我们说self是不可变的
我们查看SIL
// Stack.push(item:)
sil hidden @main.Stack.push(item: Swift.Int) -> () : $@convention(method) (Int, @guaranteed Stack) -> () {
// %0 // user: %2
// %1 // user: %3
bb0(%0 : $Int, %1 : $Stack):
debug_value %0 : $Int, let, name "item", argno 1 // id: %2
debug_value %1 : $Stack, let, name "self", argno 2 // id: %3
%4 = tuple () // user: %5
return %4 : $() // id: %5
} // end sil function 'main.Stack.push(item: Swift.Int) -> ()'
在这里self是let类型,我们根据提示加上mutating
// Stack.push(item:)
sil hidden @main.Stack.push(item: Swift.Int) -> () : $@convention(method) (Int, @inout Stack) -> () {
// %0 // users: %5, %2
// %1 // users: %6, %3
bb0(%0 : $Int, %1 : $*Stack):
debug_value %0 : $Int, let, name "item", argno 1 // id: %2
debug_value_addr %1 : $*Stack, var, name "self", argno 2 // id: %3
%4 = alloc_stack $Int // users: %5, %11, %9
store %0 to %4 : $*Int // id: %5
%6 = begin_access [modify] [static] %1 : $*Stack // users: %10, %7
%7 = struct_element_addr %6 : $*Stack, #Stack.items // user: %9
// function_ref Array.append(_:)
%8 = function_ref @Swift.Array.append(__owned A) -> () : $@convention(method) <τ_0_0> (@in τ_0_0, @inout Array<τ_0_0>) -> () // user: %9
%9 = apply %8(%4, %7) : $@convention(method) <τ_0_0> (@in τ_0_0, @inout Array<τ_0_0>) -> ()
end_access %6 : $*Stack // id: %10
dealloc_stack %4 : $*Int // id: %11
%12 = tuple () // user: %13
return %12 : $() // id: %13
} // end sil function 'main.Stack.push(item: Swift.Int) -> ()'
Stack
有@inout
来修饰。self
也由var
修饰,仔细观察,self提示我们这里是debug_value_addr
地址,而不是值类型。所以mutating
本质就是将值类型改为了引用类型。
符号表
0x100000be0 <+80>: callq 0x100000cb0 ; swiftTest.Stack.push(item: Swift.Int) -> () at main.swift:14
结构体中的方法调度方式是静态调用,在编译链接结束之后当前的函数地址就已经确定存放在了代码段.swiftTest.Stack.push(item: Swift.Int)
而函数名和变量名存储在符号表中。我们可以通过nm .../Build/Products/Debug/swiftTest (可执行文件地址)
来查看当前项目的符号
00000100000d20 T _$s9swiftTest5StackV5itemsACSaySiG_tcfC
0000000100000d00 T _$s9swiftTest5StackV5itemsACSaySiG_tcfcfA_
0000000100000c90 T _$s9swiftTest5StackV5itemsSaySiGvM
0000000100000ca0 t _$s9swiftTest5StackV5itemsSaySiGvM.resume.0
0000000100000c20 T _$s9swiftTest5StackV5itemsSaySiGvg
0000000100000ec0 S _$s9swiftTest5StackV5itemsSaySiGvpMV
0000000100000c00 T _$s9swiftTest5StackV5itemsSaySiGvpfi
0000000100000c50 T _$s9swiftTest5StackV5itemsSaySiGvs
0000000100000d30 T _$s9swiftTest5StackVACycfC
0000000100000f08 s _$s9swiftTest5StackVMF
0000000100000e20 T _$s9swiftTest5StackVMa
0000000100001010 s _$s9swiftTest5StackVMf
0000000100000ee4 S _$s9swiftTest5StackVMn
0000000100001018 S _$s9swiftTest5StackVN
0000000100000e00 t _$s9swiftTest5StackVWOh
然后通过xcrun swift-demangle s9swiftTest5StackV5itemsSaySiGvs(符号)
得到真实函数名称
$s9swiftTest5StackV5itemsSaySiGvs ---> swiftTest.Stack.items.setter : [Swift.Int]
也可以通过地址直接找到符号
nm .../Build/Products/Debug/swiftTest (可执行文件地址) | grep 0000000100000cb0(符号地址)
0000000100000cb0 T _$s9swiftTest5StackV4push4itemySi_tF
如果改为release
模式,符号表确认地址的符号就会去除。只有没有确定的才会保留,比如lazy
。
在C语言中函数的名称在符号表里就是添加了一个_,比如test()
->_test
OC中:Teacher中-(void)test
->[Teacher test]
所以在C语言和OC中是不允许方法重载的,而swift中字符串这么复杂保证的不同参数可以重载。
方法调度
对于结构体中的方法都是静态调用(直接调用),对于类中声明的方法是通过V-table
来进行调度的。
V-table
在SIL中的表示为
decl ::= sil-vtable
sil-vtable ::= 'sil_vtable' identifier '{' sil-vtable-entry* '}'
sil-vtable-entry ::= sil-decl-ref ':' sil-linkage? sil-function-na me
例:
class Teacher{
func teach(){print("teach")}
func teach1(){print("teach1")}
func teach2(){print("teach2")}
func teach3(){print("teach3")}
}
····························
sil_vtable Teacher {
#Teacher.teach!1: (Teacher) -> () -> () : @main.Teacher.teach() -> () // Teacher.teach()
#Teacher.teach1!1: (Teacher) -> () -> () : @main.Teacher.teach1() -> () // Teacher.teach1()
#Teacher.teach2!1: (Teacher) -> () -> () : @main.Teacher.teach2() -> () // Teacher.teach2()
#Teacher.teach3!1: (Teacher) -> () -> () : @main.Teacher.teach3() -> () // Teacher.teach3()
#Teacher.init!allocator.1: (Teacher.Type) -> () -> Teacher : @main.Teacher.__allocating_init() -> main.Teacher // Teacher.__allocating_init()
#Teacher.deinit!deallocator.1: @main.Teacher.__deallocating_deinit // Teacher.__deallocating_deinit
}
我们也可以通过源码查看
static void initClassVTable(ClassMetadata *self) {
const auto *description = self->getDescription();
auto *classWords = reinterpret_cast(self);
if (description->hasVTable()) {
auto *vtable = description->getVTableDescriptor();
auto vtableOffset = vtable->getVTableOffset(description);
for (unsigned i = 0, e = vtable->VTableSize; i < e; ++i)
classWords[vtableOffset + i] = description->getMethod(i);
}
....
}
这里就是通过内存偏移获得我们的方法地址存放在内存中。
extension
如果我们在extension
添加方法不会添加到我们的V-table
中,而是直接调用。
如果我们有个子类继承了父类,就会继承他的V-table
,当前编译的时候如果又编译到extension
就无法插入到子类中,无法知道到底是父类还是子类的。
final
添加了final
,表示不允许对其修饰的内容进行继承或者重新操作。
@objc
标记暴露给我们的OC。
***********swift**************
class Teacher:NSObject{
@objc func teach(){print("teach")}
}
***********oc**************
#import "OCtestSwift-Swift.h"
int main(int argc, const char * argv[]) {
Teacher *t = [Teacher new];
[t teach];
return 0;
}
这样OC中就可也以直接使用swift方法
// Teacher.teach()
sil hidden @main.Teacher.teach() -> () : $@convention(method) (@guaranteed Teacher) -> () {
// %0 // user: %1
bb0(%0 : $Teacher):
debug_value %0 : $Teacher, let, name "self", argno 1 // id: %1
%2 = tuple () // user: %3
return %2 : $() // id: %3
} // end sil function 'main.Teacher.teach() -> ()'
// @objc Teacher.teach()
sil hidden [thunk] @@objc main.Teacher.teach() -> () : $@convention(objc_method) (Teacher) -> () {
// %0 // users: %4, %3, %1
bb0(%0 : $Teacher):
strong_retain %0 : $Teacher // id: %1
// function_ref Teacher.teach()
%2 = function_ref @main.Teacher.teach() -> () : $@convention(method) (@guaranteed Teacher) -> () // user: %3
%3 = apply %2(%0) : $@convention(method) (@guaranteed Teacher) -> () // user: %5
strong_release %0 : $Teacher // id: %4
return %3 : $() // id: %5
} // end sil function '@objc main.Teacher.teach() -> ()'
通过SIL文件我们也可以看出来,teach
方法生成了两个方法,在暴露给OC的方法中又调用了swift方法。
dynamic
使属性启用Objc的动态转发功能.
@objc + dynamic
如果两个一起动就是我们的动态消息转发。symbol stub for: objc_msgSend
class Teacher{
dynamic func teach(){print("teach")}
}
extension Teacher{
@_dynamicReplacement(for: teach)
func teach1(){print("teach1")}
}
var t = Teacher()
t.teach()
···················
teach1
这个时候我们调用teach
的时候就会调用teach1
。