- 原文作者 : Jesse Squires
- 譯文出自 : 掘金翻譯計(jì)劃
- 譯者 : Dwight
- 校對(duì)者: jk77me, owenlyn
就在前幾天蠢护,我終于把項(xiàng)目遷移到了Swift2.2,在使用SE-0022建議的#selector
語(yǔ)句時(shí)养涮,我遇到了一些問(wèn)題葵硕。如果在protocol extension中使用#selector
,這個(gè)protocol必須添加@Objc
修飾符贯吓。而之前的Selector("method:")
語(yǔ)句則不需要添加懈凹。
通過(guò)協(xié)議的擴(kuò)展配置視圖控制器
為了達(dá)到本文的目的,我簡(jiǎn)化了工作中項(xiàng)目的代碼悄谐,但所有核心的思想都保留著介评。一種我經(jīng)常在swift里用的模式是:為了重用的配置寫(xiě)protocols(協(xié)議)和extensions(擴(kuò)展),特別是有Uikit的時(shí)候
假設(shè)我們有一組視圖控制器爬舰,每個(gè)控制器都需要一個(gè) view model 和 一個(gè)“取消”按鈕威沫。每一個(gè)控制器需要各自響應(yīng)
“cancel”按鈕的點(diǎn)擊事件。我們可以這樣寫(xiě):
struct ViewModel {
let title: String
}
protocol ViewControllerType: class {
var viewModel: ViewModel { get set }
func didTapCancelButton(sender: UIBarButtonItem)
}
如果就寫(xiě)成這樣洼专,那每個(gè)控制器都需要自己去添加和寫(xiě)一個(gè)一樣的取消按鈕棒掠。這樣就會(huì)有很多一樣的代碼。我們可以通過(guò)擴(kuò)展(用老的 Selector("")
語(yǔ)句)來(lái)解決:
extension ViewControllerType where Self: UIViewController {
func configureNavigationItem() {
navigationItem.leftBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .Cancel,
target: self,
action: Selector("didTapCancelButton:"))
}
}
現(xiàn)在每個(gè)符合協(xié)議的控制器都可以通過(guò)在viewDidLoad()
里調(diào)用協(xié)議的configureNavigationItem()
方法來(lái)配置取消按鈕屁商,是不是好多了~我們的控制器看起來(lái)是這樣的:
class MyViewController: UIViewController, ViewControllerType {
var viewModel = ViewModel(title: "Title")
override func viewDidLoad() {
super.viewDidLoad()
configureNavigationItem()
}
func didTapCancelButton(sender: UIBarButtonItem) {
// handle tap
}
}
這僅是一個(gè)簡(jiǎn)單的例子烟很,但我們可以想象通過(guò)這個(gè)方式制造更多復(fù)雜的配置。
把以上代碼段升級(jí)到 Swift 2.2后蜡镶,是這樣的:
extension ViewControllerType where Self: UIViewController {
func configureNavigationItem() {
navigationItem.leftBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .Cancel,
target: self,
action: #selector(didTapCancelButton(_:)))
}
}
但現(xiàn)在我們有了個(gè)問(wèn)題雾袱,一個(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
當(dāng)@objc
試圖破壞所有的東西
因?yàn)橐幌盗械脑颍?在原始的ViewControllerType
協(xié)議中,我們并不能簡(jiǎn)單的給這個(gè)方法添加一個(gè)@objc
修飾符官还。如果我們這么做了芹橡,那么所有的protocol都需要用@objc
來(lái)標(biāo)記,這將意味著:
- 所有這個(gè)protocol的父protocol都需要用
@objc
來(lái)標(biāo)記望伦。 - 所有繼承自這個(gè)protocol的protocol都會(huì)被自動(dòng)添加
@objc
林说。 - 我們?cè)趐rotocol中的結(jié)構(gòu)體(
ViewModel
)不能用Objective-C來(lái)表示。
到目前屯伞,@objc
在這里的唯一功能就是定義了一個(gè)普通的target-action selectors腿箩。盡管我們可以使用swift的強(qiáng)大功能,但是因?yàn)镃ocoa依然貫穿我們的代碼Cocoa all the way down劣摇,我們并沒(méi)有正真的在寫(xiě)純粹的swift - 除非我們開(kāi)始在各個(gè)地方引入@objc珠移。
我們?cè)谶@的例子很簡(jiǎn)單,但是想象一下更復(fù)雜的類(lèi)依賴關(guān)系圖,大量使用Swift的值類(lèi)型和當(dāng)這個(gè)協(xié)議處在多個(gè)協(xié)議的中間層時(shí)钧惧。把引入@objc
作為解決方案真是app的末日暇韧。如果我們這樣做,@objc
這種做法會(huì)讓我們的Swift代碼毫無(wú)美感并變得亂糟糟浓瞪。這會(huì)毀了所有的東西锨咙。
但是希望還是有的。
不使用@objc
來(lái)避免亂糟糟
我們大可不必讓為了讓我們的Swifit代碼能使用Objcetive-C的語(yǔ)法而使用@objc
追逮。
我們可以把protocol分解成多個(gè)protocol來(lái)去除@objc
酪刀,然后我們?cè)僦亟M這些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之前比一切都很正常,而且我們定義的原視圖控制器也沒(méi)有發(fā)生任何改變赵刑,沒(méi)有東西被破壞分衫。如果你曾經(jīng)遇到類(lèi)似的情況,或者你也想阻止@objc
帶來(lái)的破壞(你應(yīng)該這么做)般此,我強(qiáng)烈建議采用這個(gè)策略蚪战。
這并不是顯而易見(jiàn)的
現(xiàn)在的代碼,我還是覺(jué)得有點(diǎn)不爽铐懊,當(dāng)然邀桑,針對(duì)這個(gè)問(wèn)題,這就是最Swift化的答案科乎。當(dāng)Xcode突然開(kāi)始提示你并且很快的應(yīng)用它的修復(fù)方案依然會(huì)把所有都破壞掉惫谤。特別是當(dāng)Xcode提供的修復(fù)方案正中你下懷的時(shí)候杠河,這個(gè)時(shí)候拾因,上面說(shuō)的到的這類(lèi)解決方案并不能立即很清楚罢维。
最后,在做了以上那些更改之后玉吁,我意識(shí)到總的來(lái)說(shuō)這其實(shí)是一個(gè)很好的解決方案照弥。。沒(méi)有什么理由在一個(gè)地方只用一個(gè)協(xié)議进副。像ViewModelConfigurable
和 NavigationItemConfigurable
這兩個(gè)協(xié)議分工明確。把不同的協(xié)議組合在一起始終都是最優(yōu)雅、最適當(dāng)?shù)脑O(shè)計(jì)影斑。