美文网首页Swift
Swift Runtime 讲解及的使用

Swift Runtime 讲解及的使用

作者: 一滴矿泉水 | 来源:发表于2022-02-22 21:10 被阅读0次

    公司半年前做了一个组件化的项目,用到了CTMediator这个三方框架 ,主要是为了完成组件与主工程间数据传递及页面间的交互处理。其中CTMediator就是借助了Runtime的消息转发实现了这些功能 ,Runtime是Objective-C 里面最核心最重要的技术,接下来我会通过一些实际应用来进行一下介绍 。

    一、Runtime 概念

    Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在这里下到苹果维护的开源代码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。Objective-C 从三种不同的层级上与 Runtime 系统进行交互,分别是通过 Objective-C 源代码,通过 Foundation 框架的NSObject类定义的方法,通过对 runtime 函数的直接调用。大部分情况下你就只管写你的Objc代码就行,runtime 系统自动在幕后辛勤劳作着。

    • RunTime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制。
    • 对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何二义性。
    • OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。
    • 只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。

    二: 获取类信息方法

    • 获取方法列表:

        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!) ?? "没有找到你想要的成员变量")
        }
      
    • 协议列表:

        let protocalList = class_copyProtocolList(object_getClass(self),&count)
        for index in 0..<numericCast(count) {
            let protocal = protocol_getName((protocalList?[index])!)
            print("协议:",String.init(utf8String: protocal) ?? "没有找到你想要的协议")
        }
      

    三: 使用场景

    • 添加关联对象
    • 消息转发
    • 动态交换两个方法的实现
    • 在方法上增加额外功能
    • 实现NSCoding的自动归档和解档
    • 实现字典转模型的自动转换

    1)添加关联对象

    给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)消息转发()

    这里以实例方法的动态解析为例子

    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
        
           //拦截指定未实现的方法完成转发可执行下面写法
           //        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("未实现的方法,都转发到我这里了哦")
    
         }
    }
    

    被转发的方法 resolveInstanceRun

    @objc func resolveInstanceRun(){
    
           print("未实现的方法,都转发到我这里了哦")
    }
    

    方法调用

    override func viewDidLoad() {
        super.viewDidLoad()
        let Vc = TestObject()
        let Selector = NSSelectorFromString("CCCC")
        //会走转发方法
        Vc.perform(Selector)
        // 这种调用方式不会 走转发方法
        //Vc.responds(to: Selector)
    }
    

    当调用一个类的未实现的实例方法时,就会调用类的动态解析方法,然后可以在动态解析方法中转发

    3)方法交换

    对于纯粹的Swift类,由于无法拿到类的属性方法等,也就没办法进行方法的替换,但是对于继承自NSObject的类,由于集成了OC的所有特性,所以是可以利用Runtime的属性来进行方法替换,需要动态替换的方法前面 需要使用dynamic关键字。

    方法myMethod 与 myChangeMethod方法替换 class_addMethod

    class TestObject{
        @objc dynamic func myMethod(name:String,age:Int){
            print("myMethod -->\(name) 历经 --->\(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 先进行判断一下原有类中是否有要替换方法的实现
            let didAddMethod: Bool = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod!),     method_getTypeEncoding(swizzledMethod!))
            //如果 class_addMethod 返回 yes,说明当前类中没有要替换方法的实现,所以需要在父类中查找,这时候就用到     method_getImplemetation 去获取 class_getInstanceMethod 里面的方法实现,然后再进行 class_replaceMethod 来实现 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) 历经 --->\(age) 年")
    
        }
    }
    

    didFinishLaunchingWithOptions 方法中调用方法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)在方法上增加额外功能

    这个功能可以沿用方法交换的代码 ,只需要在交换的方法中调用一下原方法。

    @objc func myChangeMethod(name:String,age:Int) {
        //调用原方法(因为myChangeMethod 与 myMethod进行了方法交换,所以调用myChangeMethod实际执行的是方法交换之前myMethod方法的实现)
        myChangeMethod(name: name, age: age)
        //下面是需要实现的新功能代码 例如新功能是打印 abcd
        print("abcd")
    
    }
    

    参考链接:
    https://www.jianshu.com/p/46dd81402f63

    文章持续更新中、希望对各位有所帮助、有问题可留言 大家共同学习.

    相关文章

      网友评论

        本文标题:Swift Runtime 讲解及的使用

        本文链接:https://www.haomeiwen.com/subject/wnpglrtx.html