相對(duì)于Objective-C的Runtime機(jī)制,Swift的運(yùn)行時(shí)機(jī)制相對(duì)低調(diào)很多,Swift語(yǔ)言是用C++編寫的,Swift的核Library使用Swift編寫的.
方法調(diào)度
Objective-C采用消息發(fā)送策略,選擇器向接收器發(fā)送消息,編譯階段無(wú)法知道對(duì)象是否有對(duì)應(yīng)的方法,運(yùn)行時(shí)根據(jù)isa指針,找到對(duì)象所屬的類結(jié)構(gòu)體,然后結(jié)合類中的緩存方法列表指針和虛函數(shù)指針找到選擇器對(duì)應(yīng)的SEL選擇器類型變量,如果找到則SEL變量對(duì)應(yīng)的IMP指針找到方法實(shí)現(xiàn).如果找不到對(duì)應(yīng)的方法,則會(huì)啟動(dòng)消息轉(zhuǎn)發(fā)機(jī)制,如果仍然失敗,拋出異?;虮罎?
Swift的方法調(diào)度分為靜態(tài)調(diào)度和動(dòng)態(tài)調(diào)度兩種.
靜態(tài)調(diào)度:Swift中的struct方法調(diào)度是靜態(tài)的,執(zhí)行的時(shí)候直接跳到方法的實(shí)現(xiàn),靜態(tài)調(diào)度可以進(jìn)行inline和其他編譯器優(yōu)化.需要額外的方法來(lái)存儲(chǔ)方法信息.
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
動(dòng)態(tài)調(diào)度:Swift中Class是動(dòng)態(tài)調(diào)度的,添加方法之后Class本身在棧上分配的仍然是一個(gè)word.堆上需要額外的一個(gè)word來(lái)存儲(chǔ)Class的Type信息,在Class的Type信息中,在Class的Type信息中,存儲(chǔ)著virtual table(V-Table)。根據(jù)V-Table就可以找到對(duì)應(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運(yùn)行時(shí)依賴TypeEncoding,也就是method_getTypeEncoding返回的結(jié)果,他指定了方法的參數(shù)類型以及在函數(shù)調(diào)用時(shí)參數(shù)入棧所要的內(nèi)存空間,沒(méi)有這個(gè)標(biāo)識(shí)就無(wú)法動(dòng)態(tài)的壓入?yún)?shù)
如果Swift類沒(méi)有繼承NSObject,那么是無(wú)法通過(guò)運(yùn)行時(shí)獲取屬性和方法的.如果Swift類繼承了NSObject,屬性或方法中包含Objective-C中不存在的類型,如果說(shuō)元組,那么也是對(duì)應(yīng)的屬性或方法是無(wú)法獲取的.
定義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
}
}
測(cè)試代碼:
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")
}
}

方法交換
相對(duì)于Objective-C的方法交換,對(duì)于單獨(dú)的Swift類,是無(wú)法通過(guò)Objective-C直接交換的,對(duì)于繼承的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);
}
}
交換測(cè)試:
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í)行")
}
注意測(cè)試方法加入了dynamic特性,否則是無(wú)法通過(guò)運(yùn)行時(shí)進(jìn)行交換的,viewDidAppear是繼承Objective-C類獲得的方法,本身就被修飾為dynamic,所以能被動(dòng)態(tài)替換.
測(cè)試的交換的是寫在ViewController中的,Objective-C runtime 理論上會(huì)在加載和初始化類的時(shí)候調(diào)用兩個(gè)類方法: load 和 initialize
。出于安全性和一致性的考慮,方法交叉過(guò)程 永遠(yuǎn) 會(huì)在 load()
方法中進(jìn)行.
每一個(gè)類在加載時(shí)只會(huì)調(diào)用一次 load方法,一個(gè) initialize 方法可以被一個(gè)類和它所有的子類調(diào)用,Swift中l(wèi)oad類方法不會(huì)被runtime調(diào)用,所有可以在initialize執(zhí)行交互過(guò)程,由于initialize會(huì)執(zhí)行多次,可以通過(guò)dispatch_once確保只執(zhí)行一次.
參考資料
Swift進(jìn)階之內(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 編譯和運(yùn)行時(shí)原理初探
http://allegro.tech/2014/12/swift-method-dispatching.html
Type EnCodings
Swift Runtime分析:還像OC Runtime一樣嗎?