Swift 中的 Runtime

即便在 Swift APP 中沒有一行 Object-c 的代碼,每一個 APP 也都會在 Object-c runtime 中運行,爲動態任務分發和運行時對象關聯開啓了一個世界。更確切地說,可能在僅使用 Swift 庫的時候只運行 Swift runtime。可是使用 Objective-C runtime 這麼長時間,咱們也應該讓他充分發揮其做用。html

下面咱們將以 Swift 的視角來觀察關聯對象(associated objects])和方法交叉(method swizzling) 這兩個在運行時的技術。swift

####關聯對象(Associated Objects)安全

Swift extension 能夠給已經存在 Cocoa 類添加極爲豐富的功能,具體有: (1)添加計算實例屬性 ( computed property) 和計算類屬性性能優化

(2)定義實例方法和類方法網絡

(3)提供新的構造器app

(4)定義下標(subscript)框架

(5)定義和使用新的嵌套類型ide

(6)使一個遵照某個接口性能

相比之下, Objective-C 的 category 就遜色多了。好比說 Objective-C 中的 extension 就沒法向既有類添加屬性。優化

慶幸的是 Objective-C 的 關聯對象(Associated Objects) 能夠改善這個缺憾。例如要向一個工程裏全部的 view controllers 中添加一個 descriptiveName 屬性,咱們能夠簡單的使用 objc_get/setAssociatedObject()來填充其 get 和 set 塊:

Swift

extension UIViewController {
    private struct AssociatedKeys {
        static var DescriptiveName = "nsh_DescriptiveName"
    }

    var descriptiveName: String? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String
        }
        set {
            if let newValue = newValue {
                objc_setAssociatedObject(
                    self,
                    &AssociatedKeys.DescriptiveName,
                    newValue as NSString?,
                    UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                )
            }
        }
    }

注意,在私有嵌套 struct 中使用 static var,這樣會生成咱們所需的關聯對象鍵,但不會污染整個命名空間。

#####方法交叉(Method Swizzling)

有時爲了方便,也有多是解決某些框架內的 bug,或者別無他法時,須要修改一個已經存在類的方法的行爲。方法交叉能夠實現兩個方法的交換,至關因而用你本身寫的方法來重載原有方法,而且還可以是原有方法的行爲保持不變。

下面,咱們說一個例子,在這個例子中咱們交叉 UIViewController 的 viewWillAppear 方法,而後打印出每個在屏幕上顯示的 view。方法交叉發生在 initialize 類方法調用時(以下代碼所示);替代的實如今 nsh_viewWillAppear 方法中:

Swift
extension UIViewController {
    public override class func initialize() {
        struct Static {
            static var token: dispatch_once_t = 0
        }

        // make sure this isn't a subclass        
        if self !== UIViewController.self {
            return
        }

        dispatch_once(&Static.token) {
            let originalSelector = Selector("viewWillAppear:")
            let swizzledSelector = Selector("nsh_viewWillAppear:")

            let originalMethod = class_getInstanceMethod(self, originalSelector)
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

            let didAddMethod = class_addMethod(self, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))

            if didAddMethod {
                class_replaceMethod(self, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        }
    }

    // MARK: - Method Swizzling

    func nsh_viewWillAppear(animated: Bool) {
        self.nsh_viewWillAppear(animated)
        if let name = self.descriptiveName {
            println("viewWillAppear: \(name)")
        } else {
            println("viewWillAppear: \(self)")
        }
    }
}

load vs. initialize (Swift 版本)

Objective-C runtime 理論上會在加載和初始化類的時候調用兩個類方法: load and initialize。在講解 method swizzling 的原文中曾指出出於安全性和一致性的考慮,方法交叉過程 永遠 會在 load() 方法中進行。每個類在加載時只會調用一次 load 方法。另外一方面,一個 initialize 方法能夠被一個類和它全部的子類調用,好比說 UIViewController 的該方法,若是那個類沒有被傳遞信息,那麼它的 initialize 方法就永遠不會被調用了。

可不一樣的是,在 Swift 中 load 類方法是不會被 runtime 調用,所以 Method Swizzling 就沒有辦法來實現,可是,咱們有以下兩個方法能夠來解決:

1.在 initialize 中實現方法交叉 這種作法很安全,你只須要確保相關的方法交叉在一個 dispatch_once 中就行了(這也是最推薦的作法)。

2.在 app delegate 中實現方法交叉 不像上面經過類擴展進行方法交叉,而是簡單地在 app delegate 的 application(_:didFinishLaunchingWithOptions:) 方法調用時中執行相關代碼也是能夠的。基於對類的修改,這種方法應該就足夠確保這些代碼會被執行到。

最後,提醒你們,在不得已的狀況下才去使用 Objective-C runtime。隨便修改基礎框架或所使用的三方代碼會給項目形成很大的影響。請務必要當心哦。

文章來源:Swift&Object-c Runtime

備註:本文已經獲得原做者的贊成,受權 OneAPM 技術博客進行轉載

OneAPM Mobile Insight 以真實用戶體驗爲度量標準進行 Crash 分析,監控網絡請求及網絡錯誤,提高用戶留存。訪問 OneAPM 官方網站感覺更多應用性能優化體驗,想閱讀更多技術文章,請訪問 OneAPM 官方技術博客

相關文章
相關標籤/搜索