[译]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这两个协议分工明确。把不一样的协议组合在一块儿始终都是最优雅、最适当的设计。

相关文章
相关标签/搜索