公司半年前做了一個組件化的項目,用到了CTMediator這個三方框架 ,主要是為了完成組件與主工程間數(shù)據(jù)傳遞及頁面間的交互處理。其中CTMediator就是借助了Runtime的消息轉(zhuǎn)發(fā)實現(xiàn)了這些功能 ,Runtime是Objective-C 里面最核心最重要的技術(shù),接下來我會通過一些實際應(yīng)用來進行一下介紹 。
一、Runtime 概念
Runtime基本是用C和匯編寫的,可見蘋果為了動態(tài)系統(tǒng)的高效而作出的努力。你可以在這里下到蘋果維護的開源代碼。蘋果和GNU各自維護一個開源的runtime版本,這兩個版本之間都在努力的保持一致。Objective-C 從三種不同的層級上與 Runtime 系統(tǒng)進行交互,分別是通過 Objective-C 源代碼,通過 Foundation 框架的NSObject類定義的方法,通過對 runtime 函數(shù)的直接調(diào)用。大部分情況下你就只管寫你的Objc代碼就行,runtime 系統(tǒng)自動在幕后辛勤勞作著。
- RunTime簡稱運行時,就是系統(tǒng)在運行的時候的一些機制,其中最主要的是消息機制。
- 對于C語言,函數(shù)的調(diào)用在編譯的時候會決定調(diào)用哪個函數(shù),編譯完成之后直接順序執(zhí)行,無任何二義性。
- OC的函數(shù)調(diào)用成為消息發(fā)送。屬于動態(tài)調(diào)用過程。在編譯的時候并不能決定真正調(diào)用哪個函數(shù)(事實證明,在編 譯階段,OC可以調(diào)用任何函數(shù),即使這個函數(shù)并未實現(xiàn),只要申明過就不會報錯。而C語言在編譯階段就會報錯)。
- 只有在真正運行的時候才會根據(jù)函數(shù)的名稱找 到對應(yīng)的函數(shù)來調(diào)用。
二: 獲取類信息方法
-
獲取方法列表:
let methodList = class_copyMethodList(object_getClass(SwiftClass), &count) for ind in 0..<numericCast(count) { let method = method_getName(methodList![ind]) print("屬性成員方法:",String.init(NSStringFromSelector(method))) }
-
屬性成員變量:
let IvarList = class_copyIvarList(object_getClass(SwiftClass),&count) for index in 0..<numericCast(count) { let Ivar = ivar_getName((IvarList?[index])!) print("屬性成員變量:",String.init(utf8String: Ivar!) ?? "沒有找到你想要的成員變量") }
-
協(xié)議列表:
let protocalList = class_copyProtocolList(object_getClass(self),&count) for index in 0..<numericCast(count) { let protocal = protocol_getName((protocalList?[index])!) print("協(xié)議:",String.init(utf8String: protocal) ?? "沒有找到你想要的協(xié)議") }
三: 使用場景
- 添加關(guān)聯(lián)對象
- 消息轉(zhuǎn)發(fā)
- 動態(tài)交換兩個方法的實現(xiàn)
- 在方法上增加額外功能
- 實現(xiàn)NSCoding的自動歸檔和解檔
- 實現(xiàn)字典轉(zhuǎn)模型的自動轉(zhuǎn)換
1)添加關(guān)聯(lián)對象
給UIButton 添加一個count屬性
extension UIButton {
private static var ADJ_KEY: Void?
var count: Int {
get {
(objc_getAssociatedObject(self, &Self.ADJ_KEY) as? Int) ?? 0
}
set {
objc_setAssociatedObject(self, &Self.ADJ_KEY, newValue, .OBJC_ASSOCIATION_COPY)
}
}
}
使用
override func viewDidLoad() {
super.viewDidLoad()
let test = UIButton()
test.count = 200
print(test.count)
}
objc_getAssociatedObject 和 objc_setAssociatedObject
2)消息轉(zhuǎn)發(fā)()
這里以實例方法的動態(tài)解析為例子
class TestObject:NSObject{
override class func resolveInstanceMethod(_ sel: Selector!) -> Bool {
let swizzledSelector = #selector(resolveInstanceRun)
let methodName = NSStringFromSelector(sel)
print("未找到此方法"+methodName)
guard let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else {
return super.resolveInstanceMethod(sel)
}
let imp:IMP = method_getImplementation(swizzledMethod)
class_addMethod(self, sel, imp, "v@:")
return true
//攔截指定未實現(xiàn)的方法完成轉(zhuǎn)發(fā)可執(zhí)行下面寫法
// guard methodName == "CCCC" else{
// return super.resolveInstanceMethod(sel)
// }
// let swizzledSelector = #selector(resolveInstanceRun)
//
// let methodName = NSStringFromSelector(sel)
//
// print("未找到此方法"+methodName)
//
// guard let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else {
//
// return super.resolveInstanceMethod(sel)
// }
// let imp:IMP = method_getImplementation(swizzledMethod)
// class_addMethod(self, sel, imp, "v@:");
//
// return true;
}
@objc func resolveInstanceRun(){
print("未實現(xiàn)的方法,都轉(zhuǎn)發(fā)到我這里了哦")
}
}
被轉(zhuǎn)發(fā)的方法 resolveInstanceRun
@objc func resolveInstanceRun(){
print("未實現(xiàn)的方法,都轉(zhuǎn)發(fā)到我這里了哦")
}
方法調(diào)用
override func viewDidLoad() {
super.viewDidLoad()
let Vc = TestObject()
let Selector = NSSelectorFromString("CCCC")
//會走轉(zhuǎn)發(fā)方法
Vc.perform(Selector)
// 這種調(diào)用方式不會 走轉(zhuǎn)發(fā)方法
//Vc.responds(to: Selector)
}
當(dāng)調(diào)用一個類的未實現(xiàn)的實例方法時,就會調(diào)用類的動態(tài)解析方法,然后可以在動態(tài)解析方法中轉(zhuǎn)發(fā)
3)方法交換
對于純粹的Swift類,由于無法拿到類的屬性方法等,也就沒辦法進行方法的替換,但是對于繼承自NSObject的類,由于集成了OC的所有特性,所以是可以利用Runtime的屬性來進行方法替換,需要動態(tài)替換的方法前面 需要使用dynamic關(guān)鍵字。
方法myMethod 與 myChangeMethod方法替換 class_addMethod
class TestObject{
@objc dynamic func myMethod(name:String,age:Int){
print("myMethod -->\(name) 歷經(jīng) --->\(age) 年")
}
}
extension TestObject{
public class func initializeMethod(){
let originalSelector = #selector(TestObject.myMethod(name:age:))
let swizzledSelector = #selector(TestObject.myChangeMethod(name:age:))
let originalMethod = class_getInstanceMethod(self, originalSelector)
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)
//在進行 Swizzling 的時候,需要用 class_addMethod 先進行判斷一下原有類中是否有要替換方法的實現(xiàn)
let didAddMethod: Bool = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
//如果 class_addMethod 返回 yes,說明當(dāng)前類中沒有要替換方法的實現(xiàn),所以需要在父類中查找,這時候就用到 method_getImplemetation 去獲取 class_getInstanceMethod 里面的方法實現(xiàn),然后再進行 class_replaceMethod 來實現(xiàn) Swizzing
if didAddMethod {
class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
} else {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
}
@objc func myChangeMethod(name:String,age:Int) {
print("myChangeethod -->\(name) 歷經(jīng) --->\(age) 年")
}
}
didFinishLaunchingWithOptions 方法中調(diào)用方法initializeMethod
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
TestObject.initializeMethod()
return true
}
使用
override func viewDidLoad() {
super.viewDidLoad()
let Vc = TestObject()
Vc.myMethod(name: "中國", age: 5000)
// Do any additional setup after loading the view.
}
4)在方法上增加額外功能
這個功能可以沿用方法交換的代碼 ,只需要在交換的方法中調(diào)用一下原方法。
@objc func myChangeMethod(name:String,age:Int) {
//調(diào)用原方法(因為myChangeMethod 與 myMethod進行了方法交換,所以調(diào)用myChangeMethod實際執(zhí)行的是方法交換之前myMethod方法的實現(xiàn))
myChangeMethod(name: name, age: age)
//下面是需要實現(xiàn)的新功能代碼 例如新功能是打印 abcd
print("abcd")
}
參考鏈接:
http://www.lxweimin.com/p/46dd81402f63
文章持續(xù)更新中、希望對各位有所幫助、有問題可留言 大家共同學(xué)習(xí).