前几天在 RxSwift 项目下看到了个 Issue 826 (opens new window) 。我表示对这个 Issue 很感兴趣,用一句话概括,JegnuX (opens new window) 希望调整 Swift 3 版本的 RxSwift 中对应 Cocoa 扩展的 API,比如将 rx_tap
替换成 rx.tap
。
先来概述一下 JegnuX 的描述。
当然,你可以直接看 Issue 826 (opens new window) 。
JegnuX 指出用 rx_
前缀作为 Cocoa 的扩展不是非常优雅的风格,而且这并不适合 Swift 代码风格。无需怀疑的是为一个类添加一个方法是需要前缀的,具体可以参见瞄大的如何打造一个让人愉快的框架 (opens new window)。可供选择的前缀有。
rx_tap
,目前最常见的方案。rxTap
,使用标准的驼峰式写法,然而这很困惑,这不能很好的表达这是一个 rx 的扩展方法,很容易误解为某个一般的方法。reactiveTap
,相比 rxTap
增加了易读性,然而并没有什么卵用。rx.tap
,卧槽,如此完美,用命名空间的形式表达了这是一个属于 rx 的扩展方法。为什么前缀 rx.
就是一个好的设计?
主要一点就是这里用了类似代理 proxy 的方法,具体的实现内容在对应的 proxy 方法中。
同时 Swift 标准库中 LazySequence
也是采用了类似方案。比如。
myArray.map { ... }
myArray.lazy.map { ... }
在 RxCocoa 中我们也可以采用相似的语法。
myButton.rx.tap.subscribeNext { ... }
JegnuX 曾写了一篇 safe-collection-subsripting-in-swift (opens new window),解释为什么 myArray.safe[2]
比 myArray[safe: 2]
更好,同时给出了自己的实现方案。
此外,JegnuX 还在 Issue 826 (opens new window) 中给出了代码实现的细节,以及在内存管理上要注意的问题。
我在这里也总结了一些优点。
来对比一下 rx_tap
和 rx.tap
的区别。
rx_tap
就是一个属性,UIButton
的一个属性,再去理解一下属性名,才可以知道这应该是 Rx 的一个扩展属性。
rx.tap
就不同了,分开来看,rx
是 UIButton
的一个属性,tap
是 Rx<UIButton> 的一个属性,这样一来就很清晰的表明了。
UIButton
支持 Rx
扩展Rx
的 base
是 UIButton
时,具有一个 tap
属性此外,Swift 标准库中所有的 public 方法都没有采用下划线的方式进行区分代码。正如 JegnuX 描述的,
lazy
是一个扩展方法系列,这里存在一个层级的感觉。array -> lazy -> map
,而非array -> map: lazy
的形式。而这里的rx_tap
就是button -> tap: rx
,rx.tap
就变成了button -> rx -> tap
。
这里以之前在链家做的 Swift 下的 UITableView (opens new window) 分享为例,我用泛型加协议写了一个扩展方法,方便注册 Cell 和重用 Cell 。类似的方法可以参考使用泛型来优化 TableView Cells 的使用体验 (opens new window)。
我写的扩展方法如下。
public extension UITableView {
public func st_dequeueReusableCell<Cell: UITableViewCell where Cell: IdentifiableType>(withIdentifierable identifierable: Cell.Type, for indexPath: NSIndexPath) -> Cell {
return dequeueReusableCellWithIdentifier(identifierable.identifier, forIndexPath: indexPath) as! Cell
}
}
考虑到这可能作为框架使用,我加上了 st_
前缀表达这是一个属于 Swifty
框架的方法。此时如果我写了个 s***t
的方法,这个方法也可能在自动补全中提示出来,这很尴尬,很明显这不是我们想要的。换到 st.
的形式就没问题了。
嗯,这很优雅。唯一可惜的是,这在 Xcode 7 中存在一个 bug ,比如我在使用 Label 的某个 st
系列方法时,自动补全的情况变得很不乐观。
显然我写的扩展方法是没有问题的。
extension Swifty where Base: UITableView {
public func dequeueReusableCell<Cell: UITableViewCell where Cell: IdentifiableType>(withIdentifierable identifierable: Cell.Type, for indexPath: NSIndexPath) -> Cell {
return base.dequeueReusableCellWithIdentifier(identifierable.identifier, forIndexPath: indexPath) as! Cell
}
}
我限定了该方法只能用在 UITableView
下。我在 Xcode 8 beta 5 下写了一遍,没有什么问题,正确的提示,这应该是 Xcode 7 的 bug 。
此外,采用这样的写法会减少 Xcode 崩溃,毕竟自动补全的方法搜索范围大大减少了。
用一个 stuct/class 将需要处理的 class 封装一下就可以。
public struct Swifty<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}
添加扩展属性。
public extension NSObjectProtocol {
public var st: Swifty<Self> {
return Swifty(self)
}
}
之后只需要为 Swifty
添加 extension
。
extension Swifty where Base: UITableView {
//...
}
从 _
迁移到 .
是件很容易的事情。
rx.tap
是更优雅的写法,相比从 Objective-C 时代留下来的“下划线”方法将在 Swift 框架中逐渐消失,RxSwift 的 Swift 3 版本已经决定采用了该方案,此外需要注意的是,如果你有使用 SnapKit ,可能会好奇为什么 SnapKit (opens new window) 为什么迟迟没有出 Swift 3 分支,其实 feature/0.40.0 (opens new window) 分支就是对应的 Swift 3 分支,而语法的变动也从 _
变成了 .
。
box.snp.makeConstraints { (make) -> Void in
make.width.height.equalTo(50)
make.center.equalTo(self.view)
}