交换方法Method Swizzling[swift]

作者: Dariel | 来源:发表于2018-11-16 11:38 被阅读44次

    1. dispatch_once替代方案

    OC中用来保证代码块只执行一次的dispatch_once在swfit中已经被废弃了,取而代之的是使用static let,let本身就带有线程安全性质的.

    例如单例的实现.

    final public class MySingleton {
        static let shared = MySingleton()
        private init() {}
    }
    

    但如果我们不想定义常量,需要某个代码块执行一次呢?

    private lazy var takeOnceTime: Void = {
        // 代码块...
    }()
    
    _ = takeOnceTime
    

    定义一个懒加载的变量,防止在初始化的时候被执行.后面加一个void,为了在_ = takeOnceTime赋值时不耗性能,返回一个Void类型.

    lazy var改为static let也可以,为了使用方便,我们用一个类方法封装下

    class ClassName {
        private static let takeOnceTime: Void = {
            // 代码块...
        }()
        static func takeOnceTimeFunc() {
            ClassName.takeOnceTime
        }
    }
    
    // 使用
    ClassName.takeOnceTimeFunc()
    

    这样就可以做到和dispatch_once一样的效果了.

    2. 被废弃的+load()和+initialize()

    我们都知道OC中两个方法+load()+initialize().

    +load(): app启动的时候会加载所有的类,此时就会调用每个类的load方法.
    +initialize(): 第一次初始化这个类的时候会被调用.

    然而在目前的swift版本中这两个方法都不可用了,那现在我们要在这个阶段搞事情该怎么做? 例如method swizzling.

    JORDAN SMITH大神给出了一种很巧解决方案.UIApplication有一个next属性,它会在applicationDidFinishLaunching之前被调用,这个时候通过runtime获取到所有类的列表,然后向所有遵循SelfAware协议的类发送消息.

    extension UIApplication {
        private static let runOnce: Void = {
            NothingToSeeHere.harmlessFunction()
        }()
        override open var next: UIResponder? {
            // Called before applicationDidFinishLaunching
            UIApplication.runOnce
            return super.next
        }
    }
    
    protocol SelfAware: class {
        static func awake()
    }
    class NothingToSeeHere {
        static func harmlessFunction() {
            let typeCount = Int(objc_getClassList(nil, 0))
            let types = UnsafeMutablePointer<AnyClass>.allocate(capacity: typeCount)
            let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types)
            objc_getClassList(autoreleasingTypes, Int32(typeCount))
            for index in 0 ..< typeCount {
                (types[index] as? SelfAware.Type)?.awake()
            }
            types.deallocate()
        }
    }
    

    之后任何遵守SelfAware协议实现的+awake()方法在这个阶段都会被调用.

    3. 交换方法 Method Swizzling

    黑魔法Method Swizzling在swift中实现的两个困难点

    • swizzling 应该保证只会执行一次.
    • swizzling 应该在加载所有类的时候调用.

    分别在上面给出了解决方案.

    下面给出了两个示例供参考:

    protocol SelfAware: class {
        static func awake()
        static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector)
    }
    
    extension SelfAware {
        
        static func swizzlingForClass(_ forClass: AnyClass, originalSelector: Selector, swizzledSelector: Selector) {
            let originalMethod = class_getInstanceMethod(forClass, originalSelector)
            let swizzledMethod = class_getInstanceMethod(forClass, swizzledSelector)
            guard (originalMethod != nil && swizzledMethod != nil) else {
                return
            }
            if class_addMethod(forClass, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!)) {
                class_replaceMethod(forClass, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
            } else {
                method_exchangeImplementations(originalMethod!, swizzledMethod!)
            }
        }
    }
    
    class NothingToSeeHere {
        static func harmlessFunction() {
            let typeCount = Int(objc_getClassList(nil, 0))
            let types = UnsafeMutablePointer<AnyClass>.allocate(capacity: typeCount)
            let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass>(types)
            objc_getClassList(autoreleasingTypes, Int32(typeCount))
            for index in 0 ..< typeCount {
                (types[index] as? SelfAware.Type)?.awake()
            }
            types.deallocate()
        }
    }
    extension UIApplication {
        private static let runOnce: Void = {
            NothingToSeeHere.harmlessFunction()
        }()
        override open var next: UIResponder? {
            UIApplication.runOnce
            return super.next
        }
    }
    

    SelfAwareextension中为swizzlingForClass做了默认实现,相当于一层封装.

    1. 给按钮添加点击计数
    extension UIButton: SelfAware {
        static func awake() {
            UIButton.takeOnceTime
        }
        private static let takeOnceTime: Void = {
            let originalSelector = #selector(sendAction)
            let swizzledSelector = #selector(xxx_sendAction(action:to:forEvent:))
            
            swizzlingForClass(UIButton.self, originalSelector: originalSelector, swizzledSelector: swizzledSelector)
        }()
        
        @objc public func xxx_sendAction(action: Selector, to: AnyObject!, forEvent: UIEvent!) {
            struct xxx_buttonTapCounter {
                static var count: Int = 0
            }
            xxx_buttonTapCounter.count += 1
            print(xxx_buttonTapCounter.count)
            xxx_sendAction(action: action, to: to, forEvent: forEvent)
        }
    }
    
    2. 替换控制器的viewWillAppear方法
    extension UIViewController: SelfAware {
        static func awake() {
            swizzleMethod
        }
        private static let swizzleMethod: Void = {
            let originalSelector = #selector(viewWillAppear(_:))
            let swizzledSelector = #selector(swizzled_viewWillAppear(_:))
            
            swizzlingForClass(UIViewController.self, originalSelector: originalSelector, swizzledSelector: swizzledSelector)
        }()
        
        @objc func swizzled_viewWillAppear(_ animated: Bool) {
            swizzled_viewWillAppear(animated)
            print("swizzled_viewWillAppear")
        }
    }
    
    本文收录于 SwiftTips

    如有疑问,欢迎留言 :-D

    相关文章

      网友评论

      • BryantHe:厉害了,支持,很喜欢你之前的动画文章,可惜demo的swift版本低了

      本文标题:交换方法Method Swizzling[swift]

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