Swift 命名空间形式扩展的实现

99lout960com 8年前
   <p>Swift 的 extension 机制很强大,不仅可以针对自定义的类型,还能作用于系统库的类型,甚至基础类型比如 Int 。当在对系统库做 extension 的时候,就会涉及到一个命名冲突的问题。Objective-C 时代的通行解决办法是在扩展方法名字的最前面加上 XXX_ 形式的前缀。这种形式不但解决了命名冲突的问题,而且增强了代码可读性。一旦阅读到这种风格的方法名,就知道是非系统的实现。Swift 社区最初的一段时间内,也是按照这种命名方式来做的。</p>    <h2>Swifty</h2>    <p>在前缀形式的扩展使用了一段时间之后,大家渐渐觉得前缀形式的 Objective-C 风格不再适合 Swift。在命名方式上,社区掀起了一股 Swifty 化的风潮。WWDC 2016 的 Session 403 Swift API Design Guidelines 中详细阐述了 Swifty 风格的命名规则,而 Swift 3 的大量 API 的改动,也是按照这种风格进行的演进。很多开源的 Swift 陆陆续续在接下来的版本中,抛弃了之前的前缀命名形式,改用了 namespace 形式的命名。比如 RxSwift 的 rx_ => rx. ,SnapKit 的 snp_ => snp.</p>    <p>对我们来说,实际的开发过程中,也经常会对系统库中的已有类型做自定义的扩展,如果有一种通用的形式,来实现这种扩展,那就太好了。</p>    <h2>原理</h2>    <p>namespace 形式扩展的原理,就是对原类型进行一层封装。在 Swift 中,这个封装类型使用的是 Struct,然后,对这个 Struct 进行自定义的方法扩展。</p>    <h2>代码实现</h2>    <p>以我刚写的一个开源库 HandOfTheKing 举例,这个库的名字起源于冰与火之歌中的’国王之手’,主要功能是提供一个基于 RxSwift 和 SnapKit 的 iOS App 开发环境,并包含一些 iOS 开发中的实用扩展,比如链式的 UI 布局代码实现。其中的 HandOfTheKing/Namespace.swift 文件里,包含了命名空间形式扩展的实现。源码如下:</p>    <pre>  <code class="language-swift">public protocol NamespaceWrappable {      associatedtype WrapperType      var hk: WrapperType { get }      static var hk: WrapperType.Type { get }  }    public extension NamespaceWrappable {      var hk: NamespaceWrapper<Self> {          return NamespaceWrapper(value: self)      }        static var hk: NamespaceWrapper<Self>.Type {          return NamespaceWrapper.self      }  }    public protocol TypeWrapperProtocol {      associatedtype WrappedType      var wrappedValue: WrappedType { get }      init(value: WrappedType)  }    public struct NamespaceWrapper<T>: TypeWrapperProtocol {      public let wrappedValue: T      public init(value: T) {          self.wrappedValue = value      }  }</code></pre>    <h2>使用方式</h2>    <p>如果只想使用的话,把上面的源码拖到你的工程里,然后修改 hk 为任何你想要的前缀即可。扩展使用方式也很简单,举一个实际使用情况的例子,如果想要对 String 扩展一个名为 test 的方法,方法返回自身内容。除了写成方法,为了更便于使用,定义成计算属性也是可以的,代码如下:</p>    <pre>  <code class="language-swift">extension String: NamespaceWrappable { }  extension TypeWrapperProtocol where WrappedType == String {      var test: String {          return wrappedValue      }  }</code></pre>    <p>然后就可以按照下面的形式来使用这个扩展</p>    <pre>  <code class="language-swift">let testStr = "foo".hk.test  print(testStr)</code></pre>    <p>在对 TypeWrapperProtocol 这个协议做 extension 时, where 后面的 WrappedType 约束可以使用 == 或者 : ,两者是有区别的。如果扩展的是值类型,比如 String,Date 等,就必须使用 == ,如果扩展的是类,则两者都可以使用,区别是如果使用 == 来约束,则扩展方法只对本类生效,子类无法使用。如果想要在子类也使用扩展方法,则使用 : 来约束。</p>    <p>还有一些注意的地方</p>    <ul>     <li>由于 namespace 相当于将原来的值做了封装,所以如果在写扩展方法时需要用到原来的值,就不能再使用 self ,而应该使用 wrappedValue 。</li>     <li>对类型扩展实现 NamespaceWrappable 协议,只需要写一次。如果对 UIView 已经写了 NamespaceWrappable 协议实现,则 UILabel 不需要再写。实际上写了之后,编译会报错。</li>     <li>如果在实现的 func 前加上 static 关键字,可以扩展出静态方法。</li>    </ul>    <h2>代码分析</h2>    <p>解释一下实现的代码,由于使用了 protocol 和 generic 来实现,这里的代码不是很容易理解。</p>    <p>首先是定义了一个 NamespaceWrappable 协议,这个协议代表了支持 namespace 形式的扩展。并紧接着给这个协议 extension 了默认实现。这样实现了这个协议的类型就不需要自行实现协议所约定的内容了。</p>    <p>NamespaceWrappable 协议的默认实现返回了 NamespaceWrapper 这个带有泛型的 Struct。同时这个 Struct 实现了 TypeWrapperProtocol 协议。而 TypeWrapperProtocol 协议也带有泛型,而这两个泛型相互关联。这样就形成了最终的写法。</p>    <p>如果没有 TypeWrapperProtocol 这个协议,是可以直接对 NamespaceWrapper 这个泛型的 Struct 进行扩展的,对 Objective-C 的类来说这样的写法没有问题,但当尝试对 Swift 的值类型进行扩展时,会产生编译错误,比如下面这两种写法的代码:</p>    <pre>  <code class="language-swift">extension NamespaceWrapper where T == String {  }    extension NamespaceWrapper where T: String {  }</code></pre>    <p>会产生不同的编译错误,有兴趣可以尝试。为了统一,则多封装了 TypeWrapperProtocol 这个协议。</p>    <h2>参考资料</h2>    <ul>     <li><a href="/misc/goto?guid=4959714381571601004" rel="nofollow,noindex">Swift API Design Guidelines</a></li>    </ul>    <p> </p>    <p>来自:https://blog.nswebfrog.com/2017/03/23/swift-namespace/</p>    <p> </p>