就在前幾天,我終于把項(xiàng)目遷移到了Swift2.2,在使用SE-0022建議的#selector語句時(shí),我遇到了一些問題。如果在protocol extension中使用#selector,這個(gè)protocol必須添加@Objc修飾符。而之前的Selector("method:")語句則不需要添加。
為了達(dá)到本文的目的,我簡化了工作中項(xiàng)目的代碼,但所有核心的思想都保留著。一種我經(jīng)常在swift里用的模式是:為了重用的配置寫protocols(協(xié)議)和extensions(擴(kuò)展),特別是有Uikit的時(shí)候
假設(shè)我們有一組視圖控制器,每個(gè)控制器都需要一個(gè) view model 和 一個(gè)“取消”按鈕。每一個(gè)控制器需要各自響應(yīng)
“cancel”按鈕的點(diǎn)擊事件。我們可以這樣寫:
struct ViewModel { let title: String } protocol ViewControllerType: class { var viewModel: ViewModel { get set } func didTapCancelButton(sender: UIBarButtonItem) }如果就寫成這樣,那每個(gè)控制器都需要自己去添加和寫一個(gè)一樣的取消按鈕。這樣就會(huì)有很多一樣的代碼。我們可以通過擴(kuò)展(用老的 Selector("") 語句)來解決:
extension ViewControllerType where Self: UIViewController { func configureNavigationItem() { navigationItem.leftBarButtonItem = UIBarButtonItem( barButtonSystemItem: .Cancel, target: self, action: Selector("didTapCancelButton:")) } }現(xiàn)在每個(gè)符合協(xié)議的控制器都可以通過在viewDidLoad() 里調(diào)用協(xié)議的configureNavigationItem() 方法來配置取消按鈕,是不是好多了~我們的控制器看起來是這樣的:
class MyViewController: UIViewController, ViewControllerType { var viewModel = ViewModel(title: "Title") override func viewDidLoad() { super.viewDidLoad() configureNavigationItem() } func didTapCancelButton(sender: UIBarButtonItem) { // handle tap } }
這僅是一個(gè)簡單的例子,但我們可以想象通過這個(gè)方式制造更多復(fù)雜的配置。
把以上代碼段升級到 Swift 2.2后,是這樣的:
extension ViewControllerType where Self: UIViewController { func configureNavigationItem() { navigationItem.leftBarButtonItem = UIBarButtonItem( barButtonSystemItem: .Cancel, target: self, action: #selector(didTapCancelButton(_:))) } }但現(xiàn)在我們有了個(gè)問題,一個(gè)新的編譯錯(cuò)誤
Argument of '#selector' refers to a method that is not exposed to Objective-C. Fix-it Add '@objc' to expose this method to Objective-C
因?yàn)橐幌盗械脑颍?在原始的ViewControllerType協(xié)議中,我們并不能簡單的給這個(gè)方法添加一個(gè)@objc修飾符。如果我們這么做了,那么所有的protocol都需要用@objc來標(biāo)記,這將意味著:
到目前,@objc在這里的唯一功能就是定義了一個(gè)普通的target-action selectors。盡管我們可以使用swift的強(qiáng)大功能,但是因?yàn)镃ocoa依然貫穿我們的代碼Cocoa all the way down,我們并沒有正真的在寫純粹的swift - 除非我們開始在各個(gè)地方引入@objc。
我們在這的例子很簡單,但是想象一下更復(fù)雜的類依賴關(guān)系圖,大量使用Swift的值類型和當(dāng)這個(gè)協(xié)議處在多個(gè)協(xié)議的中間層時(shí)。把引入@objc作為解決方案真是app的末日。如果我們這樣做,@objc這種做法會(huì)讓我們的Swift代碼毫無美感并變得亂糟糟。這會(huì)毀了所有的東西。
但是希望還是有的。
我們大可不必讓為了讓我們的Swifit代碼能使用Objcetive-C的語法而使用@objc。
我們可以把protocol分解成多個(gè)protocol來去除@objc,然后我們再重組這些protocol。事實(shí)上,我們可以讓編譯器順利編譯和避免更改任何視圖控制器的代碼。
第一步,我們把protocol拆成2個(gè)。ViewModelConfigurable 和NavigationItemConfigurable。把ViewControllerType里的extension放到NavigationItemConfigurable。
protocol ViewModelConfigurable { var viewModel: ViewModel { get set } } @objc protocol NavigationItemConfigurable: class { func didTapCancelButton(sender: UIBarButtonItem) }最終,我們可以把原ViewControllerType protocol定義成typealias 。
typealias ViewControllerType = protocol<ViewModelConfigurable, NavigationItemConfigurable>
和遷移到Swift2.2之前比一切都很正常,而且我們定義的原視圖控制器也沒有發(fā)生任何改變,沒有東西被破壞。如果你曾經(jīng)遇到類似的情況,或者你也想阻止@objc帶來的破壞(你應(yīng)該這么做),我強(qiáng)烈建議采用這個(gè)策略。
現(xiàn)在的代碼,我還是覺得有點(diǎn)不爽,當(dāng)然,針對這個(gè)問題,這就是最Swift化的答案。當(dāng)Xcode突然開始提示你并且很快的應(yīng)用它的修復(fù)方案依然會(huì)把所有都破壞掉。特別是當(dāng)Xcode提供的修復(fù)方案正中你下懷的時(shí)候,這個(gè)時(shí)候,上面說的到的這類解決方案并不能立即很清楚。
最后,在做了以上那些更改之后,我意識(shí)到總的來說這其實(shí)是一個(gè)很好的解決方案。。沒有什么理由在一個(gè)地方只用一個(gè)協(xié)議。像ViewModelConfigurable 和NavigationItemConfigurable這兩個(gè)協(xié)議分工明確。把不同的協(xié)議組合在一起始終都是最優(yōu)雅、最適當(dāng)?shù)脑O(shè)計(jì)。