谈谈Swift面向协议编程

CarrieDvi 2年前
   <h2>从一个具体需求说起</h2>    <p>应用中有多个页面内的 UICollectionViewCell 需要实现一个相同的小动画:被选中时,先缩小到原来的0.8倍,再回弹到0.9倍。动画本身实现起来不难:</p>    <pre>  <code class="language-swift">func selectWithBounce(select:Bool, animated:Bool = true){          let bounce = CAKeyframeAnimation(keyPath: "transform")            let origin = CATransform3DIdentity          let smallest = CATransform3DMakeScale(0.8, 0.8, 1)          let small = CATransform3DMakeScale(0.9, 0.9, 1)            let originValue = NSValue(CATransform3D: origin)          let smallestValue = NSValue(CATransform3D:smallest)          let smallValue = NSValue(CATransform3D:small)            if animated {              bounce.duration = 0.2              bounce.removedOnCompletion = false              if select {                  bounce.values = [originValue, smallestValue, smallValue]                  self.layer.addAnimation(bounce, forKey: "bounce")              }else{                  bounce.values = [smallestValue, originValue]                  self.layer.addAnimation(bounce, forKey: "bounce")              }          }          if select {              self.layer.transform = small          }else{              self.layer.transform = origin          }      }</code></pre>    <p>然而不同的页面有不同的 UICollectionViewCell 子类,怎样方便地让它们都能复用这个动画实现呢?</p>    <h2>面向对象的复用方式</h2>    <ul>     <li>继承</li>    </ul>    <p>如果用面向对象的思维解决问题,最容易想到的就是定义个一个继承自 UICollectionViewCell 的父类,实现这个动画,然后所有需要这个动画的cell都继承它。</p>    <p>能解决问题,但缺点也很明显:如果再来一个实现其他功能的方法需要复用,就没有办法了。Swift/Objective-C只能单继承,如果把一段实现另一个功能的代码也放到这个父类里,就引入了不必要的耦合。继承一个类,就必须接受这个类的所有代码,两部分代码没有关系却必须捆绑销售,代码就开始僵化了。</p>    <p>有的项目里定义了继承 UIViewController 的父类,实现了很多功能,要求项目里所有的页面都要继承它。这种僵化的毛病就很明显了,下面的子类代码全都依赖这个父类,想抽出来复用非常难。</p>    <ul>     <li>组合</li>    </ul>    <p>组合优于继承这是老生常谈了,用在这里就要定义一个类专门负责做这个动画,然后把 layer 作为方法参数或者成员变量传进来。缺点嘛,就是有点麻烦。这种小工具类多了,要写很多胶水代码。</p>    <ul>     <li>Extension/Category</li>    </ul>    <p>严格意义上讲这个不能算典型的面向对象,而是Swift/Objective-C特有的功能。实现就不说了。缺点也是Extension/Category固有的,给一个类加上这东西会污染所有的对象,即使他们根本不需要这个功能。</p>    <h2>面向协议的复用方式</h2>    <p>协议扩展这个特性的引入,使得Swift支持了一种新的编程范式:面向协议编程。针对某个要实现的功能,我们可以使用协议定义出接口,然后利用协议扩展提供默认的实现。</p>    <p>上代码:</p>    <pre>  <code class="language-swift">protocol BounceSelect {      func selectWithBounce(select:Bool, animated:Bool)  }    extension BounceSelect where Self:UICollectionViewCell {        func selectWithBounce(select:Bool, animated:Bool = true){          let bounce = CAKeyframeAnimation(keyPath: "transform")            let origin = CATransform3DIdentity          let smallest = CATransform3DMakeScale(0.8, 0.8, 1)          let small = CATransform3DMakeScale(0.9, 0.9, 1)            let originValue = NSValue(CATransform3D: origin)          let smallestValue = NSValue(CATransform3D:smallest)          let smallValue = NSValue(CATransform3D:small)            if animated {              bounce.duration = 0.2              bounce.removedOnCompletion = false              if select {                  bounce.values = [originValue, smallestValue, smallValue]                  self.layer.addAnimation(bounce, forKey: "bounce")              }else{                  bounce.values = [smallestValue, originValue]                  self.layer.addAnimation(bounce, forKey: "bounce")              }          }          if select {              self.layer.transform = small          }else{              self.layer.transform = origin          }      }  }</code></pre>    <p>这样,一个 UICollectionViewCell 需要这个功能,只需要声明遵守了这个协议即可,其他什么都不用做,直接就可以调用 func selectWithBounce(select:Bool, animated:Bool) 这个方法:</p>    <pre>  <code class="language-swift">class XYZCollectionViewCell : UICollectionViewCell, BounceSelect {      ...  }    let cell: XYZCollectionViewCell  cell.selectWithBounce(true)</code></pre>    <p>遵守某个协议的类的对象调用协议声明的方法时,如果类本身没有提供实现,协议扩展提供的默认实现会被调用。</p>    <p>事实上,我们还可以进一步解除耦合。这个方法依赖的只有layer,不一定要是UICollectionViewCell。</p>    <p>另外我们可以允许用户自定义动画的时长,并提供默认的时长。</p>    <p>稍微改下代码:</p>    <pre>  <code class="language-swift">protocol BounceSelect {      var layer:CALayer {get}      var animationDuration:NSTimeInterval {get}      func selectWithBounce(select:Bool, animated:Bool)    }    extension BounceSelect {        var animationDuration:NSTimeInterval {          return 0.2      }        func selectWithBounce(select:Bool, animated:Bool = true){          let bounce = CAKeyframeAnimation(keyPath: "transform")            let origin = CATransform3DIdentity          let smallest = CATransform3DMakeScale(0.8, 0.8, 1)          let small = CATransform3DMakeScale(0.9, 0.9, 1)            let originValue = NSValue(CATransform3D: origin)          let smallestValue = NSValue(CATransform3D:smallest)          let smallValue = NSValue(CATransform3D:small)            if animated {              bounce.duration = animationDuration              bounce.removedOnCompletion = false              if select {                  bounce.values = [originValue, smallestValue, smallValue]                  self.layer.addAnimation(bounce, forKey: "bounce")              }else{                  bounce.values = [smallestValue, originValue]                  self.layer.addAnimation(bounce, forKey: "bounce")              }          }          if select {              self.layer.transform = small          }else{              self.layer.transform = origin          }      }  }</code></pre>    <p>这个协议通过 var layer:CALayer {get} 定义了接入的条件,通过 func selectWithBounce(select:Bool, animated:Bool) 方法的声明和实现,定义了它的功能。它就像一个插件,不管是 UIView 还是 NSView,只要提供一个 CALayer 属性,都可以方便地接入这个功能。这样就可以在不同的类之间复用这段代码了,解除了对类型的依赖。这是继承和</p>    <p>Extension/Category都无法做到的。</p>    <p>如果是CALayer本身呢?很简单:</p>    <pre>  <code class="language-swift">class XYZLayer : CALayer, BounceSelect {        //如果想修改动画时长就重载这个属性      var animationDuration:NSTimeInterval {          return 0.3      }      var layer:CALayer {          return self      }  }</code></pre>    <p>面向协议编程的好处在于,通过协议+扩展实现一个功能,能够定义所需要的 <strong>充分必要</strong> 条件,不多也不少。这样就最大程度减少了耦合。使用者可以像搭积木一样随意组合这些协议,写一个class或struct来完成复杂的功能。实际上,Swift的标准库几乎是everything is starting out as a protocol。</p>    <p>传统的协议(比如Objective-C的protocol,Java的Interface)只能定义接口,不能复用实现,遵守同一个协议的不同的类,只能分别实现协议接口,使用场景受限了很多。Swift只是多了一个协议扩展的特性,但却带来了编程范式的进化。</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/4544b8837556</p>    <p> </p>