Swift runtime
[TOC]
前言
我的另一篇文章關(guān)于Objective-C
iOS Runtime簡介,可以參考著閱讀宫盔。
我們都知道Objective-C
是一門動態(tài)語言融虽,其動態(tài)特性主要依賴于runtime
。但是Swift
是一門靜態(tài)語言灼芭,那么我們常常說起的Swift runtime
是怎么回事呢有额?其實純Swift
是沒有動態(tài)性的,Swift runtime
是指Swift
中的屬性或者方法在一些修飾符修飾彼绷,繼承自NSObject
的時候巍佑,可以使用runtime API
進(jìn)行相關(guān)的調(diào)用,下面我們就來詳細(xì)的分析一下寄悯。
1. Runtime API
本章節(jié)不是介紹Runtime API
的萤衰,而是通過Runtime API
獲取Swift
類中的方法和屬性。
1.1 初步探索
首先我們創(chuàng)建一個macOS
命令行工程猜旬,語言選擇Swift
脆栋,然后在main.swift
中定義一個Swift
類洒擦,代碼如下:
class Teacher {
var age: Int = 18
func teach(){
print("teach")
}
}
然后編寫一個test
方法椿争,在該方法中使用Runtime API
去獲取上面定義的類方法和屬性列表。(注意在Swift
中使用Runtime
的時候是不需要import
的)
func test(){
var methodCount:UInt32 = 0
let methodlist = class_copyMethodList(Teacher.self, &methodCount)
for i in 0..<numericCast(methodCount) {
if let method = methodlist?[i]{
let methodName = method_getName(method);
print("方法列表:\(String(describing: methodName))")
}else{
print("not found method");
}
}
var count:UInt32 = 0
let proList = class_copyPropertyList(Teacher.self, &count)
for i in 0..<numericCast(count) {
if let property = proList?[i]{
let propertyName = property_getName(property);
print("屬性列表:\(String(utf8String: propertyName)!)")
}else{
print("not found property");
}
}
print("test run")
}
test()
運(yùn)行熟嫩,打印結(jié)果:
根據(jù)打印結(jié)果我們可以知道秦踪,此時并沒有打印任何方法和屬性的列表。
接下來我們修改一下我們的類:
class Teacher {
@objc var age: Int = 18
@objc func teach(){
print("teach")
}
}
此時我們可以看到即打印了方列表也打印了屬性列表。但是在Swift 方法(函數(shù))調(diào)度我們提到過椅邓,如果在Swift
類中只是添加了@objc
修飾并不能OC
中被調(diào)用舍扰。如果需要在OC
中調(diào)用還需要繼承自NSObject
,那么我們修改代碼如下:
class Teacher: NSObject {
var age: Int = 18
func teach(){
print("teach")
}
}
此處只是打印了一個init
方法希坚,我們再次修改類
class Teacher: NSObject {
@objc var age: Int = 18
@objc func teach(){
print("teach")
}
}
運(yùn)行边苹,打印結(jié)果:
此時我們可以看到我們想要的都打印了,其實為了在Swift
方法中具有動態(tài)性裁僧,還可以給方法添加dynamic
關(guān)鍵字進(jìn)行修飾个束。打印結(jié)果與上面是一樣的。關(guān)于方法的調(diào)度加了什么關(guān)鍵字會有不同的調(diào)度方式聊疲,具體還是去參考我的另一篇文章Swift 方法(函數(shù))調(diào)度
說了這些有什么用呢茬底?其實從最開始的純Swift
類的什么都沒打印到最后打印了方法列表和屬性列表,我們正在一步一步的引入Runtime
在Swift
中怎么使用获洲,以及其關(guān)聯(lián)性阱表。
1.2 在objc源碼工程中調(diào)試
下面我們打開一份可編譯的objc
源碼,沒有的可以去LGCooci的GitHub下載objc4_debug贡珊。由于升級了Xcode
此時我使用的是objc4-881.2
新建一個SwiftTest Target
最爬,語言選擇Swift
,將剛才的代碼粘貼過去门岔。運(yùn)行爱致,查看打印結(jié)果與剛才的一致。
這里將代碼修改成純Swift
代碼寒随,然后跟一下源碼糠悯,以獲取方法列表為例,獲取屬性也差不多妻往。首先搜索class_copyMethodList
互艾,在objc-runtime-new.mm
文件中,添加如下斷點讯泣。
注意:首先給我們的test
方法中的class_copyMethodList
那行代碼添加斷點纫普,待執(zhí)行到那里的時候在打開上圖所示的斷點。到上圖所示的斷點后判帮,我們查看一下cls
然后點擊data()
跳轉(zhuǎn)進(jìn)去局嘁,在里面我們就可以看到objc_class
的源碼,這里的superclass
就是指向父類的指針晦墙,cache
是方法調(diào)度時的緩存悦昵,bits
中通過ro
和rw
存儲著方法和屬性。關(guān)于這些可以參考我的其他關(guān)于Objective-C
底層原理分析的文章晌畅。iOS OC 類原理
這里我們就打印一下superclass
但指,結(jié)果如下:
我們可以看到,在一個Swift
類中,如果什么都不繼承棋凳,它的默認(rèn)基類是SwiftObject
拦坠。返回class_copyMethodList
函數(shù)中,我們看獲取到的方法列表剩岳。
我們可以看到這個methods
是個二維數(shù)組贞滨,至于里面為什么都是0呢?因為我修改類的時候沒加@objc
所以取不到拍棕,然后就都是0了晓铆。加上@objc
就有值了。
2. SwiftObject
在上文中我們發(fā)現(xiàn)绰播,在一個什么都不繼承的Swift
類中骄噪,默認(rèn)繼承的基類是SwiftObject
,下面我們就去Swift
源碼中搜索一下蠢箩。
SWIFT_RUNTIME_EXPORT @interface SwiftObject<NSObject> {
@private
Class isa;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
}
// The members of the HeapObject header that are not shared by a
// standard Objective-C instance
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
InlineRefCounts refCounts
在SwiftObject.h
文件中我們找到了上面的代碼链蕊,我們可以看到這里是遵守了NSObject
協(xié)議的。而且在Swift
源碼中也有一個TargetAnyClassMetadata
有著部分與OC
中objc_class
類似的結(jié)構(gòu)谬泌。
因為Swift
是一門替代Objective-C
的語音滔韵,所以有著與OC
相似的底層也是很正常的,畢竟要慢慢過渡呵萨,兩者之間互通是必要的過程奏属。
另外在objc
源碼中我們也可以看到很多Swift
的身影,比如swift_class_t
就是一個很典型的例子潮峦。繼承自objc_class
,又有著很多Swift
特有的屬性勇婴。
struct swift_class_t : objc_class {
uint32_t flags;
uint32_t instanceAddressOffset;
uint32_t instanceSize;
uint16_t instanceAlignMask;
uint16_t reserved;
uint32_t classSize;
uint32_t classAddressOffset;
void *description;
// ...
void *baseAddress() {
return (void *)((uint8_t *)this - classAddressOffset);
}
};
那么我們不禁會有一個疑問忱嘹,既然底層是能夠互通的,那么為什么在OC
中使用Swift
類的時候還要集成自NSObject
耕渴?
其實就是通過NSObject
聲明后拘悦,編譯器才能判斷當(dāng)前的類是一個與OC
交互的類。
3. 總結(jié)
關(guān)注Swift Runtime
就分析這么多了橱脸,其實應(yīng)用起來跟OC
的差別也不大础米,下面做個簡單的總結(jié):
- 對于純
Swift
來說是沒有動態(tài)特性的,因為Swift
是一門靜態(tài)語言 - 方法和屬性在不加任何修飾符的情況下不具備所謂的
Runtime
特性 -
Swift
中的方法調(diào)度主要是函數(shù)表調(diào)度V-Table
- 對于純
Swift
類添诉,我們給方法和屬性添加@objc
標(biāo)識的情況下屁桑,可以通過Runtime API
拿到方法和屬性列表,但是還不能在OC
中調(diào)度 - 對于繼承自
NSObject
的類來說栏赴,如果想要動態(tài)的獲取當(dāng)前屬性和方法蘑斧,必須在其聲明前添加@objc
關(guān)鍵字 - 如果需要使用方法交換需要添加
dynamic
標(biāo)識。如果不添加則不能通過Runtime API
進(jìn)行調(diào)用 - 在
swift
底層源碼中有一個SwiftObject
類有著與OC
類似的底層結(jié)構(gòu),在objc
源碼中也有一個swift_class_t
結(jié)構(gòu)體竖瘾,繼承自objc_class
沟突,因此swift
與oc
能夠有良好的交互