Method Swizzling 为什么要先调用 class_addMethod?

先上个 Swift 中的 demoMethod Swizzling

Swift 中的实现

其实 Swift 中实现原理和 OC 基本一致,只是苹果爸爸不再允许在 Swift 中使用+load()+initialize()方法,这当然难不倒各种大神,那么我就做次农夫山泉。。。

Swizzling

先抽取 swizzling 的实现到NSObject的扩展当中:

extension NSObject {
    static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
        guard let originalMethod = class_getInstanceMethod(forClass, originalSelector),
              let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) else {
            return
        }
        
        let isAddSuccess = class_addMethod(forClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
        if isAddSuccess {
            class_replaceMethod(forClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }
}
复制代码

可以看到核心实现和 OC 是完全一致的,那么剩下的就是模拟 OC 版本实现中的+load()dispatch_once

dispatch_once

我们用viewDidLoad来做个dispatch_once的示范:

extension UIViewController {
    static func swizzleViewDidLoad() {
        _ = self.swizzleMethod
    }
    
    @objc func swizzled_viewDidLoad() {
        swizzled_viewDidLoad()
        print("嘻嘻")
    }
    
    private static let swizzleMethod: Void = {
        let originalSelector = #selector(viewDidLoad)
        let swizzledSelector = #selector(swizzled_viewDidLoad)
        swizzlingForClass(UIViewController.self, originalSelector: originalSelector, swizzledSelector: swizzledSelector)
    }()
}
复制代码

在 Swift 中static let这样声明的变量其实已经用到dispatch_once,而且static自带lazy属性,要在封装函数swizzleViewDidLoad被调用时候才调用。

+load()

OC 中+load()方法会在类被装载时调用,确保需要用到的方法都是被 Swizzling 过的。Swift 中可以在AppDelegateinit方法中手动调用 swizzle 方法模拟+load()实现。

class AppDelegate: UIResponder, UIApplicationDelegate {
    override init() {
        super.init()
        UIViewController.swizzleViewDidLoad()
    }
}
复制代码

为什么要先调用 class_addMethod?

class_addMethod这个方法是很容易被人忽视的,对于 Swizzling 一节中的代码,还有一种常见的写法:

extension NSObject {
    static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
        guard let originalMethod = class_getInstanceMethod(forClass, originalSelector),
              let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector) else {
            return
        }

        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}
复制代码

这种方式就只是简单的直接交换了originalMethodswizzledMethod。乍一看貌似没有问题(其实最开始我绞尽脑汁也没想清楚到底哪里不对。。。),但是为什么各路大神都是用的第一种方式呢?网上有种说法:

要先尝试添加原 selector 是为了做一层保护,因为如果这个类没有实现原始方法"originalSel" ,但其父类实现了,那 class_getInstanceMethod 会返回父类的方法。这样 method_exchangeImplementations 替换的是父类的那个方法,这当然不是你想要的。所以我们先尝试添加 originalSel ,如果已经存在,再用 method_exchangeImplementations 把原方法的实现跟新的方法实现给交换掉。

其实这种说法已经算是比较明确问题所在了,但是愚笨的我还是没有想通到底为什就“这当然不是你想要的”了呢。

又是一番绞尽脑汁。。。终于 Biuer 的一下想通了

在举栗子前引用一段对 Selectors、Methods 和 Implementations 理解:

理解 selector, method, implementation 这三个概念之间关系的最好方式是:在运行时,类(Class)维护了一个消息分发列表来解决消息的正确发送。每一个消息列表的入口是一个方法(Method),这个方法映射了一对键值对,其中键值是这个方法的名字 selector(SEL),值是指向这个方法实现的函数指针 implementation(IMP)。 Method swizzling 修改了类的消息分发列表使得已经存在的 selector 映射了另一个实现 implementation,同时重命名了原生方法的实现为一个新的 selector。

假设父类有个方法method,子类未重写method方法,子类的中想要拿来替换的方法为swizzledMethod

  1. 用第二种方式进行方法交换

    • 在子类的实例中调用method方法时,确实按预期正常运行的
    • 在父类的实例中调用method方法时,就开始崩溃了。因为方法交换后,method方法的IMP其实和子类swizzledMethodIMP进行了交换,此时等同于父类调用子类方法,当然会崩溃。
  2. 用第一种方式进行方法交换

    class_addMethod先判断了子类中是否有method方法

    • 如果有,则添加失败,直接进行交换
    • 如果没有,则添加成功,将swizzledMethodIMP赋值给method这个Selector,然后在将methodIMP(其实是父类中的实现)赋值给swizzledMethod这个Selector

转载于:https://juejin.im/post/5cb6df44e51d456e6f45c6f1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TableView 无数据 runtime method swizzling 是一种常用的技术手段,用于在 TableView 中没有数据时,自动地替换原有的方法实现来展示自定义的占位图或提示信息。 在 iOS 开发中,当 TableView 没有数据时,通常会显示一张空白的背景或者一些提示文字,告诉用户当前没有任何数据。而使用 runtime method swizzling 技术,我们可以在 TableView 的相关方法中插入自定义的代码,从而实现自动切换显示空白背景或者提示信息。 具体的实现步骤如下: 1. 创建一个自定义的占位图或提示信息视图,以便在没有数据时显示在 TableView 上。 2. 通过 runtime method swizzling 技术,将 TableView 的 reloadData 方法替换为我们自定义的方法实现。 3. 在自定义的方法实现中,判断 TableView 数据源的数量,如果为零,则将自定义的占位图或提示信息视图添加到 TableView 上,并将 TableView 的背景设置为透明。 4. 如果数据源数量不为零,则将 TableView 的背景设置为默认的 TableView 背景,并调用原有的 reloadData 方法来刷新 TableView。 使用 runtime method swizzling 技术来实现 TableView 无数据时的自定义占位图或提示信息的展示可以提高开发效率,减少了代码的重复编写。同时,由于是替换方法的实现,所以不会对原有的代码产生太多影响,维护成本也较小。但是需要注意的是,使用 runtime method swizzling 技术需要谨慎,遵循苹果官方的 API 规范,以免引发一些潜在的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值