歡迎加入QQ討論群258996829
我心依舊 頭像
蘋果1袋
1
我心依舊

如何在 Swift 中高效地使用 Method Swizzling

發(fā)布時間:2016-03-30 14:36  回復:0  查看:2660   最后回復:2016-03-30 14:36  

作者:uraimo,原文鏈接,原文日期:2015-10-23
譯者:小鐵匠Linus;校對:100mango;定稿:Channe

示例程序可以從 Github 上下載。

(譯者注:譯者也寫了一篇關于 Method Swizzling 的文章 Runtime 之 Method Swizzling,順便寫了兩個小 demo 來對比 Objective-C 和 Swift 之間 Method Swizzling 的異同,示例代碼傳送門

Method Swizzling 在 Objective-C 或其他語言中是一種很有名的技術,用來支持動態(tài)方法派發(fā)。

Method Swizzling 通過改變特定 selector(方法)與實際實現(xiàn)之間的映射,在 runtime 時將一個方法的實現(xiàn)替換成其它方法的實現(xiàn)。

如何在 Swift 中高效地使用 Method Swizzling

雖然這看起來非常方便,但是這個功能也有其缺點。在 runtime 執(zhí)行這類更改時,你就不能在編譯時利用那些可用的安全檢查了。因此,應該小心使用 Method Swizzling。

NSHipster 上有一篇關于如何在 Objective-C 中使用 Method Swizzling 的文章(譯者注:南峰子的技術博客有這篇文章的譯文)(其他詳情也可以看這里)以及 Stackoverflow 上有一些如何使用 Method Swizzling 的討論。

Swift 關于方法派發(fā)是使用靜態(tài)方法的,但有些情形可能需要用到 Method Swizzling。

在 Swift 中使用 Method Swizzling 之前,讓我再次重申一下這種技術還是盡量少用,只有當你的問題不能用 Swift 的方式解決,也不能用子類、協(xié)議或擴展解決時才使用。

正如 NSHipster 上另一篇文章描述的那樣,在 Swift 中對一個來自基本框架(Foundation、UIKit 等)的類使用 Method Swizzling 與 Objective-C 沒什么區(qū)別。

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

        // 確保不是子類
        if self !== UIViewController.self {
            return
        }

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

            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 newViewWillAppear(animated: Bool) {
        self.newViewWillAppear(animated)
        if let name = self.descriptiveName {
            print("viewWillAppear: \(name)")
        } else {
            print("viewWillAppear: \(self)")
        }
    }
}

在這個例子中,應用中的每個 UIViewController 都會執(zhí)行額外的操作,而原始的 viewWillAppear 方法會被保護起來不執(zhí)行,這種情形只能通過 Method Swizzling 來實現(xiàn)。

viewWillAppear 方法的實現(xiàn)會被替換成 initialize 中 newViewWillAppear 方法的實現(xiàn)。值得注意的是,在 swizzle 后,會遞歸調用 newViewWillAppear 方法,以替換之前原始的 viewWillAppear 方法。

與 Objective-C 的第一個不同之處就是 swizzling 不在 load 方法里執(zhí)行。

加載一個類的定義時,會調用 load 方法,因此這地方適合執(zhí)行 Method Swizzling 。

但是 load 方法只在 Objective-C 里有,而且不能在 Swift 里重載,不管怎么試都會報編譯錯誤。接下來執(zhí)行 swizzle 最好的地方就是 initialize 了,這是調用你的類中第一個方法前的地方。

所有對修改方法執(zhí)行的操作放在 dispatch_once 中,這是為了確保這個過程只執(zhí)行一次。

這些都是你要知道的關于繼承自基本框架或 Objective-C 橋接類的。如果你要在純 Swift 類中正確使用 Method Swizzling 的話,就需要將下面這些注意事項記在心上。

Swift 自定義類中使用 Method Swizzling

要在 Swift 自定義類中使用 Method Swizzling 有兩個必要條件:

  • 包含 swizzle 方法的類需要繼承自 NSObject

  • 需要 swizzle 的方法必須有動態(tài)屬性(dynamic attribute)

更多關于為什么必須需要這些可以在蘋果的文檔里查看:

需要動態(tài)派發(fā)

當 @objc 屬性將你的 Swift API 暴露給 Objective-C runtime 時,不能確保屬性、方法、初始器的動態(tài)派發(fā)。Swift 編譯器可能為了優(yōu)化你的代碼,而繞過 Objective-C runtime。當你指定一個成員變量 為 dynamic 時,訪問該變量就總會動態(tài)派發(fā)了。因為在變量定義時指定 dynamic 后,會使用 Objective-C runtime 來派發(fā)。

動態(tài)派發(fā)是必須的。但是,你必須使用 dynamic 修飾才能知道在運行時 API 的實現(xiàn)被替換了。舉個例子,你可以在 Objective-C runtime 使用 method_exchangeImplementations 方法來交換兩個方法的實現(xiàn)。假如 Swift 編譯器將方法的實現(xiàn)內聯(lián)(inline)了或者訪問去虛擬化(devirtualize)了,這個交換過來的新的實現(xiàn)就無效了。

說白了,如果你想要替換的方法沒有聲明為 dynamic 的話,就不能 swizzle。

翻譯成代碼就是這樣:

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


extension TestSwizzling {
    
    //在 Objective-C 中,我們在 load() 方法進行 swizzling。但Swift不允許使用這個方法。
    override class func initialize()
    {
        struct Static
        {
            static var token: dispatch_once_t = 0;
        }
        
        // 只執(zhí)行一次
        dispatch_once(&Static.token)
        {
                let originalSelector = Selector("methodOne");
                let swizzledSelector = Selector("methodTwo");
                
                let originalMethod = class_getInstanceMethod(self, originalSelector);
                let swizzledMethod = class_getInstanceMethod(self, swizzledSelector);
                
                method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    }
    
    func methodTwo()->Int{
        // swizzling 后, 該方法就不會遞歸調用
        return methodTwo()+1
    }
}

var c = TestSwizzling()
print(c.methodOne())  //2
print(c.methodTwo())  //1

這個簡單的例子中,在 TestSwizzling 對象第一個方法被調用前,methodOne 和 methodTwo 的實現(xiàn)被相互替換了。

結束語

正如你看到的,在 Swift 中還是可以使用 Method Swizzling 的,只是我的意見是,絕大部分時候不應該出現(xiàn)在實際的產品代碼里。如果你想通過 swizzling 快速解決問題的話,那更好的辦法就是想出一個更好的架構,重構你的代碼。

本文由 SwiftGG 翻譯組翻譯,已經獲得作者翻譯授權,最新文章請訪問 http://swift.gg。

您還未登錄,請先登錄

熱門帖子

最新帖子

?