使用 Swift 時,若是是自定義的 Protocol,能夠經過 Extension 來提供部分方法的默認實現,但系統原有的 Protocol 卻不行,大概是由於系統的 Protocol 是 Objective-C 實現的緣故。html
但爲 Protocol 提供默認實如今某些時候是很實用的。好比 iOS 開發中,Cocoa 框架裏經常使用的 TableView 使用時通常須要實現 UITableViewDataSouce 和 UITableViewDelegate。而 UITableViewDelegate 和 UITableViewDataSource 裏面的一部分方法,又是能夠抽離的,但剩下的一部分方法,使用得不多,且通常用起來都是有自定義的須要。那麼有沒有一種方法能夠爲一部分方法提供默認通用的實現,而又不影響其他的方法呢?ios
這篇文章,筆者想分享一種經過消息轉發的方式來實現差很少功能的方法。git
本文假設讀者已經瞭解 Runtime 基礎,如不瞭解可自行閱讀 Objective-C Runtime 1小時入門教程。github
引用網上這張經典的圖,能夠知道在消息處理沒法處理以前,有三個環節能進行搶救。第一步判斷是否有動態添加方法,第二步能夠對消息處理者重定向,第三步能夠在返回方法簽名的狀況下,對封裝消息的相關信息的 NSInvocation 進行處理。swift
經過上面的消息轉發的步驟,筆者的思路是,利用重定向,將 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
實現了 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 也能夠用一樣的方式進行擴展,提供一部分方法的默認實現。