Swift runtime

  • swift support limited runtime feature, because complier use Vtable (similar to C++) providing better static features and efficiency.
  • if want to use Objc runtime, we need subclass from NSObject

  • In short there are Dynamic and Static types of method call dispatching.

  • Static - the function address to be called is determined in the compilation time, so that expense of such call is similar to C-function calling. This mechanism is used for private methods or final classes methods call dispatching.

  • Dynamic dispatching is mechinism which allows to implement polymorphism concept of OOP - the function address to be called is determined in running time. Swift has two subtypes of it:

  • Obj-C - you already described in the question. This mechanism is used when object inherits from NSObject or calling method has @objc prefix.

  • Virtual table based (like in C++) - there is similar witness tables. What it does during method call dispatching is just single arithmetic operation - calculation of actual function address based on function offset in the base class witness table and the object class witness table location. So that's relatively chip operation compating to Obj-C. It explains why "pure" Swift approximates to C++ performance.

If you don't mark you method with private keyword or your class is not final and same time you class is "pure" Swift (it does not inherit NSObject) then this virtual table based mechanism is used. It means that all the methods by default are virtual.

Methods swizzling

replace method in runtime

在 Swift 中对一个来自基本框架(Foundation、UIKit 等)的类使用 Method Swizzling 与 Objective-C 没什么区别。

// from http://www.jianshu.com/p/4ee4529a52d2
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()
    }
}

要在 Swift 自定义类中使用 Method Swizzling 有两个必要条件:

  • 包含 swizzle 方法的类需要继承自 NSObject
  • 需要 swizzle 的方法必须有动态属性(dynamic attribute)

需要动态派发

当 @objc 属性将你的 Swift API 暴露给 Objective-C runtime 时,不能确保属性、方法、初始器的动态派发。Swift 编译器可能为了优化你的代码,而绕过 Objective-C runtime。当你指定一个成员变量 为 dynamic 时,访问该变量就总会动态派发了。因为在变量定义时指定 dynamic 后,会使用 Objective-C runtime 来派发。

动态派发是必须的。但是,你必须使用 dynamic 修饰才能知道在运行时 API 的实现被替换了。举个例子,你可以在 Objective-C runtime 使用method_exchangeImplementations方法来交换两个方法的实现。假如 Swift 编译器将方法的实现内联(inline)了或者访问去虚拟化(devirtualize)了,这个交换过来的新的实现就无效了。

说白了,如果你想要替换的方法没有声明为 dynamic 的话,就不能 swizzle。

class TestSwizzling : NSObject {
    dynamic func methodOne(_ num: Int) -> Int{
        print("methodOne")
        return 1
    }
}

extension TestSwizzling {
    func methodTwo(_ num: Int) -> Int{
        print("methodTwo")
        return 2
    }
}

let doSwizzling: (TestSwizzling.Type) -> Void = { (aClass) -> Void in
    let originalSelector = #selector(aClass.methodOne(_ :))
    let swizzledSelector = #selector(aClass.methodTwo(_ :))

    let originalMethod = class_getInstanceMethod(aClass, originalSelector)
    let swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);

    method_exchangeImplementations(originalMethod, swizzledMethod);
}


var c = TestSwizzling()
doSwizzling(type(of: c))
c.methodOne(0) // print: methodTwo
c.methodTwo(0) // print: methodOne

http://swift.gg/2016/03/29/effective-method-swizzling-with-swift/

results matching ""

    No results matching ""