iOS runtime swift swizzling

2019-08-23 17:56:24 浏览数 (1)

iOS runtime swift中的Swizzling方法交换

背景知识

Swift 是一种强类型语言。即默认类型是安全的静态类型。纯Swift类的函数调用已经不再是OC的运行时发送消息,而是类似于C 的vtable,在编译时就确定了调用哪个函数,所以没法通过runtime获取方法,属性。Swift中的动态性可以通过OC运行时来获得,动态性最常用的就是方法替换(Method Swizzling)。

swift动态修饰符
  • @objc 将Swift函数暴露给OC运行时,但是它仍然不能保证完全动态,编译器会尝试去对它做静态优化
  • dynamic 动态功能修饰符,它能保证函数,属性可以获得动态性
Swizzling实现地方
  • load()
  • initialize()

这里我们最好是选择 load函数里面实现方法交换。 load方法是在类加载的时候就会调用,而 initialize方法是在给类发送第一个消息之前再调用,相当于懒加载一样。

创建

代码语言:javascript复制
class Person: NSObject {

    @objc dynamic func test1() {
        debugPrint("test 1")
    }
    
}

实现方法一

代码语言:javascript复制
extension UIApplication {
    private static let runOnce: Void = {
        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()
    }()
    
    open override var next: UIResponder? {
        UIApplication.runOnce
        return super.next
    }
}

/// swizzling协议 在需要交换方法的类中遵循此协议,实现方法awake
protocol SelfAware: class {
    static func awake()
}

实现:Person类遵从SelfAware协议并实现方法awake,在awake方法内实现runtime方法交换。

代码语言:javascript复制
extension Person: SelfAware {
    
    /// SwlfAware实现方法
    static func awake() {
        changeFunc()
    }
    
    static func changeFunc() {
        if let method1 = class_getInstanceMethod(Person.self, #selector(Person.test1)),
            let method2 = class_getInstanceMethod(Person.self, #selector(Person.test2)) {
            let didAddMethod = class_addMethod(Person.self, #selector(Person.test1), method_getImplementation(method2), method_getTypeEncoding(method2))
            if didAddMethod {//不存在方法test1
                class_replaceMethod(Person.self, #selector(Person.test2), method_getImplementation(method1), method_getTypeEncoding(method1))
            } else {//存在test1方法
                method_exchangeImplementations(method1, method2)
            }
            
        }
    }
    
    @objc func test2() {
        debugPrint("test 2")
    }
    
}

实现方法二

第二种方法类似于上一种,也是靠协议实现。

代码语言:javascript复制
/// swizzling协议
protocol SwizzlingProtocol {
    static func inject()
}

/// 管理类
class SwizzlingManager {
    
    /// 只会调用一次
    static let doOnece: Void = {
        //以后别的类也在此处添加XX.inject()
       Person.inject()
    }()
    
    func enableInjection() {
        SwizzlingManager.doOnece
    }
}

extension UIApplication {
    open override var next: UIResponder? {
        SwizzlingManager.enableInjection()
        return super.next
    }
}

实现:遵从协议SwizzlingProtocol实现方法inject。然后在SwizzlingManager doOnece常量里面注册。

代码语言:javascript复制
extension Person: SwizzlingProtocol {
    
    /// SwizzlingProtocol实现方法
    static func inject() {
        DispatchQueue.once {
            changeFunc()
        }
    }
    
    static func changeFunc() {
        if let method1 = class_getInstanceMethod(Person.self, #selector(Person.test1)),
            let method2 = class_getInstanceMethod(Person.self, #selector(Person.test2)) {
            let didAddMethod = class_addMethod(Person.self, #selector(Person.test1), method_getImplementation(method2), method_getTypeEncoding(method2))
            if didAddMethod {
                class_replaceMethod(Person.self, #selector(Person.test2), method_getImplementation(method1), method_getTypeEncoding(method1))
            } else {
                method_exchangeImplementations(method1, method2)
            }
            
        }
    }
    
    @objc func test2() {
        debugPrint("test 2")
    }
    
}

在swift3.0后DispatchQueue.once就已经被取消了,我们需要另外实现:

代码语言:javascript复制
extension DispatchQueue {
    private static var _onceTracker = [String]()
    
    public class func once(file: String = #file, function: String = #function, line: Int = #line, block:()->Void) {
        let token = file   ":"   function   ":"   String(line)
        once(token: token, block: block)
    }
    
    public 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()
    }
    
}

0 人点赞