歡迎加入QQ討論群258996829
一葉知秋 頭像
蘋果2袋
2
一葉知秋

iOS開發(fā)者在Swift中應(yīng)避免過度使用@objc

發(fā)布時(shí)間:2016-07-01 16:38  回復(fù):0  查看:2451   最后回復(fù):2016-07-01 16:38  

就在前幾天,我終于把項(xiàng)目遷移到了Swift2.2,在使用SE-0022建議的#selector語句時(shí),我遇到了一些問題。如果在protocol extension中使用#selector,這個(gè)protocol必須添加@Objc修飾符。而之前的Selector("method:")語句則不需要添加。

通過協(xié)議的擴(kuò)展配置視圖控制器

為了達(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

當(dāng)@objc試圖破壞所有的東西

因?yàn)橐幌盗械脑颍?在原始的ViewControllerType協(xié)議中,我們并不能簡單的給這個(gè)方法添加一個(gè)@objc修飾符。如果我們這么做了,那么所有的protocol都需要用@objc來標(biāo)記,這將意味著:

  • 所有這個(gè)protocol的父protocol都需要用@objc來標(biāo)記。
  • 所有繼承自這個(gè)protocol的protocol都會(huì)被自動(dòng)添加@objc。
  • 我們在protocol中的結(jié)構(gòu)體(ViewModel)不能用Objective-C來表示。

到目前,@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ì)毀了所有的東西。

但是希望還是有的。

不使用@objc來避免亂糟糟

我們大可不必讓為了讓我們的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ì)。

您還未登錄,請先登錄

熱門帖子

最新帖子

?