交换方法Method Swizzling[swift]

栏目: Swift · 发布时间: 6年前

内容简介:1. dispatch_once替代方案OC中用来保证代码块只执行一次的dispatch_once在swfit中已经被废弃了,取而代之的是使用static let,let本身就带有线程安全性质的.例如单例的实现.

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()
    }
}
 </anyclass>
</anyclass>

之后任何遵守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
    }
}
 </anyclass>
</anyclass>

在SelfAware的extension中为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

作者:Dariel

链接:https://www.jianshu.com/p/23ea81be5cc2


以上所述就是小编给大家介绍的《交换方法Method Swizzling[swift]》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Caching

Web Caching

Duane Wessels / O'Reilly Media, Inc. / 2001-6 / 39.95美元

On the World Wide Web, speed and efficiency are vital. Users have little patience for slow web pages, while network administrators want to make the most of their available bandwidth. A properly design......一起来看看 《Web Caching》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换