在涉及到 Objective-C 運行時時,幾乎沒有什么是完全私有的。盡管直接方法的引入稍微改變了這一點,但只要你知道如何調用這些 API,它們仍然是可訪問的。
在 Objective-C 時代,調用未公開的 SDK 方法相對簡單。由于 Objective-C 的動態特性,可以通過為私有方法聲明一個類別來實現。這些方法會在運行時自動解析。然而,Swift 是一種類型安全的語言,這種方式在 Swift 中已經不可行。
在處理復雜簽名時,常見的做法是依賴 Swift 兼容的 Foundation API,例如簡單函數或低級的 method_getImplementation 和/或 objc_msgSend。但這種方式通常會導致代碼復雜、冗長且容易出錯。
我們可以借鑒 Objective-C 中的分類技巧,定義一個包含目標方法的 @objc 協議,通過運行時為目標類動態添加一致性,然后利用 Swift 的類型轉換功能。由于 Objective-C 的動態調度特性,該協議會自動實現我們需要的現有類方法。
首先,為 NSMethodSignature 定義一個協議,該協議需要與 Objective-C 文檔中的內容相匹配。這里需要特別注意 @objc(getArgumentTypeAtIndex:) 注解。通常,編譯器會根據方法名稱生成適當的選擇器,但在某些情況下,我們可能需要手動更改生成的名稱。正確的選擇器名稱至關重要,因為它必須與底層 API 簽名完全匹配。
@objc protocol NSMethodSignaturePrivate {
@objc(getArgumentTypeAtIndex:)
func getArgumentType(at index: UInt) -> UnsafePointer
}
定義協議后,可以通過顯式的 Objective-C 運行時調用將其附加到目標類:
let methodSignature = NSClassFromString("NSMethodSignature") as! NSObject.Type
let selector = NSSelectorFromString("signatureWithObjCTypes:")
let method = class_getClassMethod(methodSignature, selector)
雖然這段代碼還缺少錯誤檢查,但它是一個很好的輔助函數候選。測試一切正常后,可以進一步完善。
在實現類導入代碼時,可以嘗試通過泛型減少協議引用的重復。例如:
func createSignature(for type: T.Type) -> T? {
// 示例代碼
}
然而,這種方法可能會遇到編譯錯誤,例如:
靜態成員 signature(objCTypes:) 不能在協議元類型 NSMethodSignaturePrivate.Protocol 上使用。
這是因為 NSMethodSignaturePrivate 是一個協議,而不是具體的符合類型。為了調用類函數 signature(objCTypes:),需要一個具體的類型。
我們可以通過 Swift 反射 API 獲取協議名稱,然后利用 objc_getProtocol 找到對應的運行時協議:
let protocolName = "NSMethodSignaturePrivate"
if let protocolObj = objc_getProtocol(protocolName) {
// 使用 protocolObj
}
Objective-C 運行時結合 Swift 的強大表現力,為開發者提供了許多可能性。通過一些技巧,我們可以訪問 Swift 限制的或私有的 API,就像在 Objective-C 中那樣。不過,需要注意的是,這種方式可能會繞過一些安全機制,因此需要謹慎使用。
完整的示例代碼可以參考以下鏈接:
https://gist.github.com/victor-pavlychko/8a896917d8c73f4dded594ab4782214e
原文鏈接: https://medium.com/@victor.pavlychko/private-apis-objective-c-runtime-and-swift-ceaeefbb6e48