Swift Runtime 講解及的使用

公司半年前做了一個組件化的項目,用到了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_getAssociatedObjectobjc_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í).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容