[譯]iOS開發者在Swift中應避免過分使用@objc

就在前幾天,我終於把項目遷移到了Swift2.2,在使用SE-0022建議的#selector語句時,我遇到了一些問題。若是在protocol extension中使用#selector,這個protocol必須添加@Objc修飾符。而以前的Selector("method:")語句則不須要添加。git

經過協議的擴展配置視圖控制器

爲了達到本文的目的,我簡化了工做中項目的代碼,但全部核心的思想都保留着。一種我常常在swift裏用的模式是:爲了重用的配置寫protocols(協議)和extensions(擴展),特別是有Uikit的時候github

假設咱們有一組視圖控制器,每一個控制器都須要一個 view model 和 一個「取消」按鈕。每個控制器須要各自響應 「cancel」按鈕的點擊事件。咱們能夠這樣寫:swift

struct ViewModel {
    let title: String
}

protocol ViewControllerType: class {
    var viewModel: ViewModel { get set }

    func didTapCancelButton(sender: UIBarButtonItem)
}
複製代碼

若是就寫成這樣,那每一個控制器都須要本身去添加和寫一個同樣的取消按鈕。這樣就會有不少同樣的代碼。咱們能夠經過擴展(用老的 Selector("") 語句)來解決:app

extension ViewControllerType where Self: UIViewController {
    func configureNavigationItem() {
        navigationItem.leftBarButtonItem = UIBarButtonItem(
            barButtonSystemItem: .Cancel,
            target: self,
            action: Selector("didTapCancelButton:"))
    }
}
複製代碼

如今每一個符合協議的控制器均可以經過在viewDidLoad()裏調用協議的configureNavigationItem() 方法來配置取消按鈕,是否是好多了~咱們的控制器看起來是這樣的:ide

class MyViewController: UIViewController, ViewControllerType {
    var viewModel = ViewModel(title: "Title")

    override func viewDidLoad() {
        super.viewDidLoad()
        configureNavigationItem()
    }

    func didTapCancelButton(sender: UIBarButtonItem) {
        // handle tap
    }
}
複製代碼

這僅是一個簡單的例子,但咱們能夠想象經過這個方式製造更多複雜的配置。ui

把以上代碼段升級到 Swift 2.2後,是這樣的:this

extension ViewControllerType where Self: UIViewController {
    func configureNavigationItem() {
        navigationItem.leftBarButtonItem = UIBarButtonItem(
            barButtonSystemItem: .Cancel,
            target: self,
            action: #selector(didTapCancelButton(_:)))
    }
}
複製代碼

但如今咱們有了個問題,一個新的編譯錯誤spa

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
複製代碼

@objc試圖破壞全部的東西

由於一系列的緣由, 在原始的ViewControllerType協議中,咱們並不能簡單的給這個方法添加一個@objc修飾符。若是咱們這麼作了,那麼全部的protocol都須要用@objc來標記,這將意味着:翻譯

  • 全部這個protocol的父protocol都須要用@objc來標記。
  • 全部繼承自這個protocol的protocol都會被自動添加@objc
  • 咱們在protocol中的結構體(ViewModel)不能用Objective-C來表示。

到目前,@objc在這裏的惟一功能就是定義了一個普通的target-action selectors。儘管咱們可使用swift的強大功能,可是由於Cocoa依然貫穿咱們的代碼Cocoa all the way down,咱們並無正真的在寫純粹的swift - 除非咱們開始在各個地方引入@objc。設計

咱們在這的例子很簡單,可是想象一下更復雜的類依賴關係圖,大量使用Swift的值類型和當這個協議處在多個協議的中間層時。把引入@objc做爲解決方案真是app的末日。若是咱們這樣作,@objc這種作法會讓咱們的Swift代碼毫無美感並變得亂糟糟。這會毀了全部的東西。

可是但願仍是有的。

不使用@objc來避免亂糟糟

咱們大可沒必要讓爲了讓咱們的Swifit代碼能使用Objcetive-C的語法而使用@objc

咱們能夠把protocol分解成多個protocol來去除@objc,而後咱們再重組這些protocol。事實上,咱們可讓編譯器順利編譯和避免更改任何視圖控制器的代碼。

第一步,咱們把protocol拆成2個。ViewModelConfigurableNavigationItemConfigurable。把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以前比一切都很正常,並且咱們定義的原視圖控制器也沒有發生任何改變,沒有東西被破壞。若是你曾經遇到相似的狀況,或者你也想阻止@objc帶來的破壞(你應該這麼作),我強烈建議採用這個策略。

這並非顯而易見的

如今的代碼,我仍是以爲有點不爽,固然,針對這個問題,這就是最Swift化的答案。當Xcode忽然開始提示你而且很快的應用它的修復方案依然會把全部都破壞掉。特別是當Xcode提供的修復方案正中你下懷的時候,這個時候,上面說的到的這類解決方案並不能當即很清楚。

最後,在作了以上那些更改以後,我意識到總的來講這實際上是一個很好的解決方案。。沒有什麼理由在一個地方只用一個協議。像ViewModelConfigurableNavigationItemConfigurable這兩個協議分工明確。把不一樣的協議組合在一塊兒始終都是最優雅、最適當的設計。

相關文章
相關標籤/搜索