Runtime探索
案例1
測試以下代碼满俗,能否將?法列表及成員屬性打印出來涵叮?
class LGTeacher {
var age: Int = 18
func teach(){
print("teach")
}
}
let t = LGTeacher()
func test(){
var methodCount: UInt32 = 0
let methodList = class_copyMethodList(LGTeacher.self, &methodCount)
for i in 0..<numericCast(methodCount) {
if let method = methodList?[i]{
let methodName = method_getName(method)
print("方法列表:\(methodName)")
}else{
print("not found method")
}
}
var count: UInt32 = 0
let proList = class_copyPropertyList(LGTeacher.self, &count)
for i in 0..<numericCast(count) {
if let property = proList?[i]{
let propertyName = property_getName(property)
print("成員屬性:\(property)")
}else{
print("not found property")
}
}
print("test run")
}
test()
//輸出以下內(nèi)容:
//test run
從運行結(jié)果來看并沒有達到預(yù)期谋国,?法列表和成員屬性都沒有打印出來
案例2
修改
案例1
哨啃,給方法和屬性添加@objc
修飾萨醒,能否打印出結(jié)果斟珊?
class LGTeacher {
@objc var age: Int = 18
@objc func teach(){
print("teach")
}
}
//輸出以下內(nèi)容:
//方法列表:teach
//方法列表:age
//方法列表:setAge:
//成員屬性:0x0000000100008510
//test run
從運行結(jié)果來看,?法列表及成員屬性全部被打印出來富纸,但
Class
沒有繼承NSObject
囤踩,所以并不能暴漏給OC
使用
案例3
修改
案例2
,將Class
繼承于NSObject
晓褪,去掉@objc
修飾堵漱,能否打印出結(jié)果?
class LGTeacher : NSObject {
var age: Int = 18
func teach(){
print("teach")
}
}
//輸出以下內(nèi)容:
//方法列表:init
//test run
從運行結(jié)果來看涣仿,只有
init
方法被打印出來勤庐。因為繼承NSObject
后,swift.h
中默認只有init
方法暴露
案例4
修改
案例3
好港,Class
繼承于NSObject
愉镰,同時給方法和屬性添加@objc
修飾,能否打印出結(jié)果钧汹?
class LGTeacher : NSObject {
@objc var age: Int = 18
@objc func teach(){
print("teach")
}
}
//輸出以下內(nèi)容:
//方法列表:teach
//方法列表:init
//方法列表:age
//方法列表:setAge:
//成員屬性:0x0000000100008518
從運行結(jié)果來看丈探,?法列表及成員屬性全部被打印出來,同時可供
OC
使用崭孤,但對于teach()
方法类嗤,依然是V_table
函數(shù)表調(diào)度糊肠,無法使用Runtime
的方法交換,因為方法此時還不具備動態(tài)特性
案例5
修改
案例4
遗锣,Class
繼承于NSObject
货裹,將@objc
修飾改為dynamic
修飾,能否打印出結(jié)果精偿?
class LGTeacher : NSObject {
dynamic var age: Int = 18
dynamic func teach(){
print("teach")
}
}
//輸出以下內(nèi)容:
//方法列表:init
//test run
從運行結(jié)果來看弧圆,還是只有
init
方法被打印出來。因為dynamic
修飾只給方法和屬性增加了動態(tài)特性笔咽,它們依然不能被OC
使用
通過上述案例搔预,得出以下結(jié)論:
Swift
是靜態(tài)語言,所以沒有動態(tài)特性叶组。?法和屬性不加任何修飾符的情況下拯田,不具備所謂的Runtime
特性,它的方法調(diào)度方式使用V_table
函數(shù)表調(diào)度- 對于純
Swift
類甩十,給?法和屬性添加@objc
修飾后船庇,可以通過Runtime API
獲取到?法和屬性列表,但是在OC
中無法進?調(diào)度侣监,例如Runtime
的方法交換- 繼承?
NSObject
的類鸭轮,如果想要動態(tài)獲取當前?法和屬性,必須在其聲明前添加@objc
關(guān)鍵字橄霉。如果想通過Runtime API
使用它們窃爷,例如Runtime
的方法交換,需要添加dynamic
關(guān)鍵字姓蜂,讓它們具備動態(tài)特性
objc源碼分析
進入
class_copyMethodList
定義按厘,先獲取當前的data
,而data
的作用就是存儲類的信息
進入
data
定義覆糟,在objc_class
里打印superclass
刻剥,輸出的是Swift
中有默認基類_SwiftObject
在
Swift
源碼中找到_SwiftObject
,發(fā)現(xiàn)它實現(xiàn)了NSObject
協(xié)議滩字。本質(zhì)上Swift
為了和OC
進行交互造虏,它保留了OC
的數(shù)據(jù)結(jié)構(gòu)
回到
objc
源碼,打印methods
麦箍,輸出一個存放method
的二維數(shù)組漓藕。使用@objc
修飾的方法就能被獲取到,因為Swift
在底層數(shù)據(jù)結(jié)構(gòu)和OC
保持部分一致
在
objc
源碼中找到swift_class_t
挟裂,繼承自objc_class
享钞,保留了父類isa
、superclass
诀蓉、cacheData
栗竖、data
四個屬性暑脆,其次才是自己的屬性
必須繼承NSObject
的原因:Swift
在底層數(shù)據(jù)結(jié)構(gòu)和OC
只保持了部分一致,通過NSObject
的聲明狐肢,標記了當前類是一個和OC
交互的類添吗。可以幫助編譯器判斷這個類在編譯過程中份名,到底應(yīng)該走哪些方法的分支碟联。因為上層API
的調(diào)用者暴露不同,所以選擇不同僵腺,在編譯器里優(yōu)化的調(diào)用方式也就不同鲤孵。
元類型
AnyObject
代表任意類的
instance
,類的類型辰如,僅類遵守的協(xié)議
class LGTeacher {
var age: Int = 18
}
var t = LGTeacher()
//此時代表的就是當前 LGTeacher 的實例對象
var t1: AnyObject = t
//此時代表的就是 LGTeacher 這個類的類型
var t2: AnyObject = LGTeacher.self
//僅類遵守的協(xié)議
protocol JSONMap: AnyObject {}
//class可遵守此協(xié)議
class LGJSONMap: JSONMap {}
上述代碼中普监,
t1
代表LGTeacher
的實例對象,t2
代表LGTeacher
類的類型丧没,JSONMap
是僅類遵守的協(xié)議鹰椒,因為LGJSONMap
是Class
類型锡移,所以可以遵守JSONMap
協(xié)議
如果是結(jié)構(gòu)體呕童,可以遵守
JSONMap
協(xié)議嗎?
JSONMap
是僅類遵守的協(xié)議淆珊,結(jié)構(gòu)體無法使用夺饲,編譯報錯
Any
代表任意類型,包括
funcation
類型或者Optional
類型
var array: [Any] = [1, "Teacher", true]
Any
包含的類型比AnyObject
更為廣泛施符,可以理解為AnyObject
是Any
的子集往声。
如果將
array
數(shù)組的元素聲明為AnyObject
類型,編譯報錯
AnyClass
代表任意實例的類型:
AnyObject.Type
public typealias AnyClass = AnyObject.Type
上述代碼是
AnyClass
的定義戳吝,類型是AnyObject.Type
T.self
T
是實例對象浩销,返回的就是它本身T
是類,那么返回的是Metadata
class LGTeacher {
var age: Int = 18
}
var t = LGTeacher()
//返回實例對象本身
var t1 = t.self
//返回LGTeacher這個類的類型听哭,metadata元類型
var t2 = LGTeacher.self
上述代碼中慢洋,
t1
返回的是實例對象本身,t2
返回的是LGTeacher.Type
陆盘,也就是LGTeacher
這個類的類型
T.Type
?種類型普筹,
T.self
的類型是T.Type
type(of:)
?來獲取?個值的動態(tài)類型
var age: Int = 18
func test(_ value : Any) {
print(type(of: value))
}
test(age)
//輸出以下內(nèi)容:
//Int
上述代碼中,
value
的靜態(tài)類型(static type)
是Any
隘马,是編譯期確定好的太防。而type(of:)
方法?來獲取?個值的動態(tài)類型(dynamic type)
,所以輸出的是Int
案例1
value
是LGTeacher
類型酸员,實際傳入的t
是LGChild
類型蜒车,在test
方法內(nèi)執(zhí)行value.teahc()
讳嘱,打印結(jié)果是什么?
class LGTeacher {
func teahc() {
print("LGTeacher teahc")
}
}
class LGChild : LGTeacher {
override func teahc() {
print("LGChild teahc")
}
}
func test(_ value : LGTeacher) {
value.teahc();
}
var t = LGChild()
test(t)
//輸出以下內(nèi)容:
//LGChild teahc
上述代碼中酿愧,
value
編譯期類型是LGTeacher
呢燥,運行時的實際類型是LGChild
,所以打印結(jié)果是LGChild teahc
案例2
value
是TestProtocol
類型寓娩,分別傳入t1
和t2
叛氨,打印結(jié)果是什么?
protocol TestProtocol {}
class LGTeacher : TestProtocol {}
func test(_ value : TestProtocol) {
print(type(of: value))
}
var t1 = LGTeacher()
var t2: TestProtocol = LGTeacher()
test(t1)
test(t2)
//輸出以下內(nèi)容:
//LGTeacher
//LGTeacher
上述代碼中棘伴,分別傳入的
t1
和t2
寞埠,運行時的實際類型都是LGTeacher
,所以打印的type(of:)
都是LGTeacher
案例3
value
是泛型焊夸,分別傳入t1
和t2
仁连,打印結(jié)果是什么?
protocol TestProtocol {}
class LGTeacher: TestProtocol {}
func test<T>(_ value : T) {
print(type(of: value))
}
var t1 = LGTeacher()
var t2: TestProtocol = LGTeacher()
test(t1)
test(t2)
//輸出以下內(nèi)容:
//LGTeacher
//TestProtocol
上述代碼中阱穗,
test
方法的參數(shù)改為泛型饭冬,兩次打印type(of:)
的結(jié)果不一樣了,t1
輸出LGTeacher
揪阶,t2
輸出TestProtocol
昌抠。因為當泛型和協(xié)議同時參與,編譯器無法推導(dǎo)出準確類型鲁僚,需要在調(diào)用type(of:)
時將value
轉(zhuǎn)換為Any
func test<T>(_ value : T) {
print(type(of: value as Any))
}
//輸出以下內(nèi)容:
//LGTeacher
//LGTeacher
修改后的代碼炊苫,打印結(jié)果都是
LGTeacher
案例4
class_getClassMethod
方法cls
參數(shù)要求傳入AnyClass
類型,如果傳入t.self
冰沙,可以獲取方法列表嗎侨艾?
如圖所示,因為t
是實例對象拓挥,t.self
返回的就是它本身唠梨,是LGTeacher
類型。而參數(shù)cls
要求傳入AnyClass
類型侥啤,也就是需要LGTeacher.Type
類型当叭,所以類型不符,編譯報錯