相對于Objective-C的Runtime機制缭受,Swift的運行時機制相對低調(diào)很多趣斤,Swift語言是用C++編寫的,Swift的核Library使用Swift編寫的.
方法調(diào)度
Objective-C采用消息發(fā)送策略幅疼,選擇器向接收器發(fā)送消息呜魄,編譯階段無法知道對象是否有對應(yīng)的方法,運行時根據(jù)isa指針构韵,找到對象所屬的類結(jié)構(gòu)體周蹭,然后結(jié)合類中的緩存方法列表指針和虛函數(shù)指針找到選擇器對應(yīng)的SEL選擇器類型變量,如果找到則SEL變量對應(yīng)的IMP指針找到方法實現(xiàn).如果找不到對應(yīng)的方法疲恢,則會啟動消息轉(zhuǎn)發(fā)機制凶朗,如果仍然失敗,拋出異诚匀或崩潰.
Swift的方法調(diào)度分為靜態(tài)調(diào)度和動態(tài)調(diào)度兩種.
靜態(tài)調(diào)度:Swift中的struct方法調(diào)度是靜態(tài)的棚愤,執(zhí)行的時候直接跳到方法的實現(xiàn),靜態(tài)調(diào)度可以進行inline和其他編譯器優(yōu)化.需要額外的方法來存儲方法信息.
struct Point{
var x:Double // 8 Bytes
var y:Double // 8 bytes
func draw(){
print("Draw point at\(x,y)")
}
}
let point1 = Point(x: 5.0, y: 5.0)
point1.draw()
print("占用內(nèi)存大小:\(MemoryLayout<Point>.size)") //16
動態(tài)調(diào)度:Swift中Class是動態(tài)調(diào)度的杂数,添加方法之后Class本身在棧上分配的仍然是一個word.堆上需要額外的一個word來存儲Class的Type信息宛畦,在Class的Type信息中,在Class的Type信息中揍移,存儲著virtual table(V-Table)次和。根據(jù)V-Table就可以找到對應(yīng)的方法執(zhí)行體.
class Point{
var x:Double // 8 Bytes
var y:Double // 8 bytes
init(x:Double,y:Double) {
self.x = x
self.y = y
}
func draw(){
print("Draw point at\(x,y)")
}
}
let point2 = Point(x: 5.0, y: 5.0)
point2.draw()
print(MemoryLayout<Point>.size) //8
方法獲取
Objective-C運行時依賴TypeEncoding,也就是method_getTypeEncoding返回的結(jié)果羊精,他指定了方法的參數(shù)類型以及在函數(shù)調(diào)用時參數(shù)入棧所要的內(nèi)存空間斯够,沒有這個標(biāo)識就無法動態(tài)的壓入?yún)?shù)
如果Swift類沒有繼承NSObject囚玫,那么是無法通過運行時獲取屬性和方法的.如果Swift類繼承了NSObject,屬性或方法中包含Objective-C中不存在的類型读规,如果說元組抓督,那么也是對應(yīng)的屬性或方法是無法獲取的.
定義TestClass和TestController:
class TestClass {
var tBool:Bool = true
var tInt:Int32 = 32
var tFloat:Float = 72.5
var tString:String = "FlyElephant"
var tObject:AnyObject? = nil
func tInterViewInfo() {
}
}
class TestController:UIViewController {
var tBool:Bool = true
var tInt:Int32 = 32
var tFloat:Float = 72.5
var tString:String = "FlyElephant"
var tObject:AnyObject? = nil
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
func tReturnVoid(view:UIView) {
}
func tReturnVoidWithBool(value:Bool) {
}
func tReturnTuple(boolValue:Bool) -> (String,Int) {
return ("FlyElephant",100)
}
func tReturnVoidWithCharacter(aCharacter:Character) {
}
func tableView(tableView:UITableView) -> Int {
return 10
}
}
測試代碼:
private func setUp1() {
let testClass:TestClass = TestClass()
showClsRuntime(cls: object_getClass(testClass))
print("\n")
let testController:TestController = TestController()
showClsRuntime(cls: object_getClass(testController))
}
func showClsRuntime(cls:AnyClass) {
print("showClsRuntime--獲取方法(FlyElephant)")
var methodNum:UInt32 = 0
let methodList = class_copyMethodList(cls, &methodNum)
for index in 0..<numericCast(methodNum) {
let method:Method = methodList![index]!
print(String(utf8String: method_getTypeEncoding(method)) ?? " ",terminator: " ")
print(String(utf8String: method_copyReturnType(method)) ?? " ",terminator: " ")
print(String(_sel: method_getName(method)),terminator: " ")
print("\n")
}
print("showClsRuntime--獲取變量(FlyElephant)")
var propertyNum:UInt32 = 0
let propertyList = class_copyPropertyList(cls, &propertyNum)
for index in 0..<numericCast(propertyNum) {
let property:objc_property_t = propertyList![index]!
print(String(utf8String: property_getName(property)) ?? " ",terminator: " ")
print(String(utf8String: property_getAttributes(property)) ?? " ",terminator: " ")
print("\n")
}
}
方法交換
相對于Objective-C的方法交換,對于單獨的Swift類束亏,是無法通過Objective-C直接交換的铃在,對于繼承的NSObject的類,也不是所有的方法都可以直接交換.
按照OC的套路定義的交換方法:
func methodSwizzle(cls:AnyClass,originalSelector:Selector,swizzledSelector:Selector) {
let originalMethod = class_getInstanceMethod(cls, originalSelector)
let swizzledMethod = class_getInstanceMethod(cls, swizzledSelector)
let didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
交換測試:
methodSwizzle(cls: object_getClass(self), originalSelector: #selector(ViewController.viewDidAppear(_:)), swizzledSelector: #selector(ViewController.fe_viewDidAppear(_:)))
methodSwizzle(cls: object_getClass(self), originalSelector: #selector(ViewController.testMethod), swizzledSelector: #selector(ViewController.fe_testMethod))
testMethod()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
func fe_viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("FlyElephant_viewDidAppear方法交換")
}
dynamic func testMethod() {
print("testMethod交換之前的執(zhí)行")
}
dynamic func fe_testMethod() {
print("fe_testMethod交換之后的執(zhí)行")
}
注意測試方法加入了dynamic特性碍遍,否則是無法通過運行時進行交換的定铜,viewDidAppear是繼承Objective-C類獲得的方法,本身就被修飾為dynamic怕敬,所以能被動態(tài)替換.
測試的交換的是寫在ViewController中的揣炕,Objective-C runtime 理論上會在加載和初始化類的時候調(diào)用兩個類方法: load 和 initialize
。出于安全性和一致性的考慮东跪,方法交叉過程 永遠 會在 load()
方法中進行.
每一個類在加載時只會調(diào)用一次 load方法畸陡,一個 initialize 方法可以被一個類和它所有的子類調(diào)用,Swift中l(wèi)oad類方法不會被runtime調(diào)用虽填,所有可以在initialize執(zhí)行交互過程丁恭,由于initialize會執(zhí)行多次,可以通過dispatch_once確保只執(zhí)行一次.
參考資料
Swift進階之內(nèi)存模型和方法調(diào)度
https://stackoverflow.com/questions/39302834/does-swift-guarantee-the-storage-order-of-fields-in-classes-and-structs/39302927#39302927
http://nshipster.cn/swift-objc-runtime/
Swift Runtime 編譯和運行時原理初探
http://allegro.tech/2014/12/swift-method-dispatching.html
Type EnCodings
Swift Runtime分析:還像OC Runtime一樣嗎斋日?