Swift3中的 Method Swizzling

作者: 一铭_ | 来源:发表于2016-10-11 17:00 被阅读2956次

    先聊聊Method Swizzling

    从 Objective-C 开始, runtime一直是解决坑爹需求和面试装逼的一大利器,然而聊起 runtime 很多人都第一个想到 Method Swizzling,.因为 Objective-C中调用方法都是动态实现的,当运行时的才确定到底执行哪个方法,而 Method Swizzling 就是利用这个特点来解决很多问题.

    现在关于 Runtime 和 Method Swizzling 的文章太多了,我推荐一篇:
    神经病院Objective-C Runtime出院第三天——如何正确使用Runtime
    @一缕殇流化隐半边冰霜 大神写的关于 runtime这几篇,看完基本对 runtime 就没什么问题了吧...

    再看看 Swift3.0中的 Method Swizzling

    先来看看 swizzling 在 Objective-C 中的注意点:(对比上文链接中)

    1.Swizzling应该总在category的 +load中执行 ( Objective-C )

    那在 Swift 中, extension 并不是运行时加载的, 因此也没有加载时候就会被调用的类似 +load 的方法. 事实上,Swift 实现的 load 并不是在 app 运行开始就被调用的。基于这些理由,我们使用另一个类初始化时会被调用的方法来进行交换:

    open override static func initialize() {
        // Method Swizzling
    }
    

    这一条部分来自喵神 swiift tips 第二版, 喵神在第三版中删除了 swizzling 这个章节,理由是这部分更多是 Objective-C的内容. 我个人觉得如果在 Swift 中还需要用 Swizzling 这种技术来实现需求, 不如用更 Swifty的方式去解决问题, 函数式或者面向协议等等等😀

    2.Swizzling应该总是在dispatch_once中执行

    那么,问题来了,在3.0版本 dispatch once 已经被废弃,这怎么办?

    刚巧的是前几天群里的老司机 @没故事的卓同学 写了篇 [译]Swift 3 中实现Dispatch once扩展

    通过给DispatchQueue实现一个扩展方法来实现 Dispatch once.
    至于没什么要 dispatch_once呢? 因为 Swizzling会改变全局状态,所以用dispatch_once来确保无论多少线程都只会被执行一次.

    3. Swift自定义类中使用 Method Swizzling

    因为Method Swizzling的实现是基于 Objective-C 的动态派发机制,所以有两条限制
    1.包含 swizzle 方法的类需要继承自 NSObject
    2.如果要 Swizzle 的是 Swift 类型的方法的话,需要将原方法和替换方法都加上 dynamic 标记,以指明它们需要使用动态派发机制


    上个 sample:

    extension UIViewController {
        open override static func initialize() {
            struct Static {
                static var token = NSUUID().uuidString
            }
    
            if self != UIViewController.self {
                return
            }
    
            DispatchQueue.once(token: Static.token) { 
                let originalSelector = #selector(UIViewController.viewWillAppear(_:))
                let swizzledSelector = #selector(UIViewController.xl_viewWillAppear(animated:))
    
                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)
                }
            }
        }
    
        func xl_viewWillAppear(animated: Bool) {
            self.xl_viewWillAppear(animated: animated)
            print("xl_viewWillAppear in swizzleMethod")
        }
    }
    
    extension DispatchQueue {
        private static var onceTracker = [String]()
    
        open class func once(token: String, block:() -> Void) {
            objc_sync_enter(self)
            defer { objc_sync_exit(self) }
    
            if onceTracker.contains(token) {
                return
            }
            
            onceTracker.append(token)
            block()
        }
    }
    

    相关文章

      网友评论

      • 冰三尺:请问下 按照这个格式给UIScrollview 写一个扩展, 交换 scrollViewDidScroll这个方法, 总是报错, 说这个方法不存在?请问这个怎么搞?
      • __________mo:initialize方法已经不能使用了
        冰三尺:load 和 initialize 都不能使用, 那是用什么来代替这两个方法呢?
        zhangPeng丶:那oc中,navigationVC把tabbarVC的设置方法写在哪啊,求指教
      • 雪雪雪雪佳佳佳佳:如果 nsobject 想 initialize 呢 在 nsobject 的extension 里面要怎么写?

      本文标题:Swift3中的 Method Swizzling

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