Swift

Swift Runtime 讲解及的使用

2022-02-22  本文已影响0人  一滴矿泉水

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

一、Runtime 概念

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

二: 获取类信息方法

三: 使用场景

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

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

上一篇下一篇

猜你喜欢

热点阅读