給 Cocoa 的系統 Protocol 提供默認實現

使用 Swift 時,若是是自定義的 Protocol,能夠經過 Extension 來提供部分方法的默認實現,但系統原有的 Protocol 卻不行,大概是由於系統的 Protocol 是 Objective-C 實現的緣故。html

但爲 Protocol 提供默認實如今某些時候是很實用的。好比 iOS 開發中,Cocoa 框架裏經常使用的 TableView 使用時通常須要實現 UITableViewDataSouce 和 UITableViewDelegate。而 UITableViewDelegate 和 UITableViewDataSource 裏面的一部分方法,又是能夠抽離的,但剩下的一部分方法,使用得不多,且通常用起來都是有自定義的須要。那麼有沒有一種方法能夠爲一部分方法提供默認通用的實現,而又不影響其他的方法呢?ios

這篇文章,筆者想分享一種經過消息轉發的方式來實現差很少功能的方法。git

Objective-C 的消息轉發

本文假設讀者已經瞭解 Runtime 基礎,如不瞭解可自行閱讀 Objective-C Runtime 1小時入門教程github

691078-9049faadbcbbeac9

引用網上這張經典的圖,能夠知道在消息處理沒法處理以前,有三個環節能進行搶救。第一步判斷是否有動態添加方法,第二步能夠對消息處理者重定向,第三步能夠在返回方法簽名的狀況下,對封裝消息的相關信息的 NSInvocation 進行處理。swift

實現 ProtocolProxy

經過上面的消息轉發的步驟,筆者的思路是,利用重定向,將 Protocol 裏全部的實現進行轉發,Protocol 若是有自定義實現者則交給它,不然交給默認實現者,若是都沒有則不處理。框架

用 Swift 簡單的實現以下:ide

class ProtocolProxy: NSObject {
    weak var customImp: AnyObject?
    weak var defaultImp: AnyObject?
    
    override func responds(to aSelector: Selector!) -> Bool {
        return defaultImp?.responds(to: aSelector) == true || customImp?.responds(to:aSelector) == true
    }
    
    override func forwardingTarget(for aSelector: Selector!) -> Any? {
        if let rawObject = customImp, rawObject.responds(to: aSelector) {
            return rawObject
        } else if let defualtObject = defaultImp, defualtObject.responds(to: aSelector) {
            return defaultImp
        } else {
            return super.forwardingTarget(for: aSelector)
        }
    }
}
複製代碼

其中 respondsToSelector 方法用於判斷方法是否有被實現者實現,有的話經過 forwardingTarget 方法進行消息處理者的重定向。ui

TableView 的實際使用

實現了 ProtocolProxy 以後,在設置 Protocol 時,就能夠設置成 ProtocolProxy,而後將默認實現和自定義實現都設置給 ProtocolProxy。舉個 TableView 的例子:spa

private let delegateKey = "delegate"
private let dataSourceKey = "dataSource"
class ProxyTableView: UITableView {
    let delegateProxy = ProtocolProxy()
    let dataSourceProxy = ProtocolProxy()
    
    var defaultDelgate: UITableViewDelegate? {
        didSet{
            delegateProxy.defaultImp = defaultDelgate
            setValue(delegateProxy, forKey: delegateKey)
        }
    }
    var defaultDataSource: UITableViewDataSource? {
        didSet{
            dataSourceProxy.defaultImp = defaultDataSource
            setValue(dataSourceProxy, forKey: dataSourceKey)
        }
    }
    
    override public var dataSource: UITableViewDataSource?{
        didSet{
            if !(dataSource is ProtocolProxy){
                dataSourceProxy.customImp = dataSource
                setValue(dataSourceProxy, forKey: dataSourceKey)
            }
        }
    }
    
    override public var delegate: UITableViewDelegate?{
        didSet{
            if !(delegate is ProtocolProxy){
                delegateProxy.customImp = delegate
                setValue(delegateProxy, forKey: delegateKey)
            }
        }
    }
}
複製代碼

ProxyTableView 持有 delegate 和 dataSource 的 ProtocolProxy,經過重寫 delegate 和 dataSource 的 didSet 屬性觀察方法,將 Protocol 的自定義實現設置給對應 ProtocolProxy,再將 ProtocolProxy 設置爲 TableView 的 Delegate 和 DataSource,這裏須要注意的是,要使用 KVC 進行設置,由於 ProtocolProxy 自己並不實現 UITableViewDelegate 和 UITableViewDataSource,並且須要判斷是否是 ProtocolProxy 避免循環設置。再加上 defaultDataSouce 和 defaultDelegate 來讓外界設置默認實現者,設置完須要從新用 KVC 設置到 TableView 裏。code

這樣的話,就能夠將一部分默認實現抽離到基類中,而對於使用者來講,僅須要實現部分自定義的方法。

好比想讓 didSelectRowAtIndexPath 的實現自定義化,能夠像例子中這樣

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let tableView = ProxyTableView(frame: view.frame, style: .plain)
        //...
        tableView.defaultDelgate = BaseTableViewDelegate()
        tableView.delegate = self
    }
}

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("CustomTableViewDelegate")
    }
}

class BaseTableViewDelegate: NSObject, UITableViewDelegate{
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("BaseTableViewDelegate")
    }
}
複製代碼

最後

完整的 Demo,別的系統 Protocol 也能夠用一樣的方式進行擴展,提供一部分方法的默認實現。

相關文章
相關標籤/搜索