IOS精选文集iOS 开发 iOS Developer

[译]Swift2的面向接口编程(Protocol-Orient

2016-03-18  本文已影响746人  Wizard1990

原文来自Raywenderlich,作者Eric Kerber,原文链接:
https://www.raywenderlich.com/109156/introducing-protocol-oriented-programming-in-swift-2

Note: 本教程需要Xcode7以及Swift2,可以在Apple官方网站下载(译者注:原文发布是Swift2还处于beta阶段)。

在WWDC2015时,Apple发布了Swift语言的第二个大的版本更新——Swift 2。新版本包含了很多新的语言特性(译者注:中文改动可以看这里,不过推荐阅读英文版改动日志),让Swift在使用的时候更加得心应手。
  众多改动之中,最引人注意的就是protocol extensions。在Swift第一版中,我们可以通过extension来为已有的classstructenum拓展功能。而在Swift 2中,我们也可以为protocol添加extension。
  可能一开始看上去这个新特性并不起眼,实际上protocol extensions非常强大,以至于可以改变Swift之前的某些编程思想。在本教程中,我们会展示如何创建并使用protocol extensions,以及随之而来的一些新技巧和面向接口编程(Protocol-oriented programming,以下简称POP)模式对代码的影响。
  你将会了解Swift团队是如何使用protocol extensions来改善Swift标准库,以及如何使用这些技巧来改善自己的代码。

准备工作

首先,创建一个新的playground。在Xcode中,选择File\New\Playground...,然后将playground命名为SwiftProtocols。platform可以选择任意选项,之后我们要构建的代码都是平台无关的。点击Next选择文件路径,最后点击Create
  新的playground打开之后,加入如下代码:

protocol Bird {
  var name: String { get }
  var canFly: Bool { get }
}

protocol Flyable {
  var airspeedVelocity: Double { get }
}

以上代码定义了一个简单的protocol——Bird。它有namecanFly两个属性。还有一个protocol叫做Flyable,定义了属性airspeedVelocity
之前,你或许会把Flyable定义成一个基类(base class),然后利用继承的方法来定义Birds以及其它“会飞”的类型,例如飞机(译者注:Flyable意为飞行物,Birds就是鸟)。而从现在开始,所有的东西都开始被定义为protocol了!
  当我们开始定义具体的类型的时候,你就会发现,这一切会让整个系统变得多么灵活。

定义Protocol-conforming类型

将下面定义struct的代码添加到playground中:

struct FlappyBird: Bird, Flyable {
  let name: String
  let flappyAmplitude: Double
  let flappyFrequency: Double
  let canFly = true

  var airspeedVelocity: Double {
    return 3 * flappyFrequency * flappyAmplitude
  }
}

这里定义了一个新的struct类型——FlappyBird。它同时满足BirdFlyable两个protocol。airspeedVelocity作为一个computed property,由flappyFrequencyflappyAmplitude计算得出。作为“会飞的鸟”,FlappyBird将canFly设置为true。
  接下来,把下面定义两个struct的代码加到playground中(译者注:Penguin是企鹅,SwiftBird可以理解为速度很快的鸟):

struct Penguin: Bird {
  let name: String
  let canFly = false
}

struct SwiftBird: Bird, Flyable {
  var name: String { return "Swift \(version)" }
  let version: Double
  let canFly = true

  // Swift is FAST!
  var airspeedVelocity: Double { return 2000.0 }
}

PenguinBird是is-a关系,但并不能飞。Aha——幸亏没有使用继承的方式,否则所有的Bird都会被定义为Flyable。对于SwiftBird,自然而然的,它速度很快,飞行速度的数值也很高。
  然而,或许你已经注意到了,目前的实现还是有一些冗余存在。每一种Bird类型必须声明canFly属性,不管它到底会不会飞。我们的系统中已经有了Flyable的概念,却并没能很好的利用起来。

用默认实现来拓展Protocols

利用protocol extensions,我们可以定义protocol的默认行为。添加下面的代码到playground中:

extension Bird where Self: Flyable {
  // Flyable birds can fly!
  var canFly: Bool { return true }
}

这段代码定义了一个Bird的extension, 并设置了canFly属性的默认行为。当实现Bird protocol的对象同时实现了Flyable protocol的时候,让canFly的值总是返回true。换句话说,“会飞的鸟”终于不用再显式的声明自己“会飞”了!


  Swift 1.2 引入了与if-let关联的where语法,Swift 2则把where进一步用于了有条件的拓展protocol。
  删除FlappyBirdSwiftBird定义里面的let canFly = true。你会发现playground依然成功build,因为protocol extension帮你处理了冗余的部分。

为什么不使用基类(Base Classes)?

Protocol extensions和默认行为(Default Behavior)可能看起来和使用基类很相似。更确切一点,很像其它一些语言中的abstract class。但在Swift中,protocol extensions主要有如下一些便利:

换句话说,protocol extensions为值类型(value types)提供了定义默认行为的方法。
  展示了struct类型的例子之后,让我们把下面的enum定义加到playground中:

 enum UnladenSwallow: Bird, Flyable {
  case African
  case European
  case Unknown

  var name: String {
    switch self {
      case .African:
        return "African"
      case .European:
        return "European"
      case .Unknown:
        return "What do you mean? African or European?"
    }
  }

  var airspeedVelocity: Double {
    switch self {
      case .African:
        return 10.0
      case .European:
        return 9.9
      case .Unknown:
        fatalError("You are thrown from the bridge of death!")
    }
  }     
}

如同其它值类型一样,你需要做的只是定义正确的属性来让UnladenSwallow满足两个protocol即可(译者注:UnladenSwallow当成是一种燕子就行)。因为它同时满足BirdFlyable,所以直接拥有canFly的默认实现。
  你难道觉得这个用了airspeedVelocity的教程不会提到Monty Python么?:](译者注:貌似是作者用了一个梗,Monty Python是国外一个著名的喜剧团体)。

Protocols的扩展

大概protocol extensions最常见的用途就是用来扩展外部的protocols了,往往要么是来自Swift标准库,要么是来自第三方库。
把下面的代码加入playground:

 extension CollectionType {
   func skip(skip: Int) -> [Generator.Element] {
     guard skip != 0 else { return [] }

    var index = self.startIndex
    var result: [Generator.Element] = []
    var i = 0
    repeat {
      if i % skip == 0 {
        result.append(self[index])
      }
      index = index.successor()
      i++
    } while (index != self.endIndex)

    return result
  }
}

这段代码定义了一个CollectionType的extension,其中包含了一个新的skip(_:)方法。这个方法会遍历collection中的内容并每次“跳过”特定个数的元素,最终返回那些没有被“跳过”的元素的collection。
  CollectionType是一个protocol。Swift中一些例如数组(arrays)和字典(Dictionaries)都实现了这个protocol。这意味着现在skip(_:)这个新方法适用于所有的CollectionType

扩展自定义的Protocols

给Swift标准库添加新行为让人兴奋。与此同时,我们也可以利用标准库的protocol extensions给自定义的类型定义默认行为。
  修改Bird protocol使得它满足BooleanType protocol:

protocol Bird: BooleanType {

满足BooleanType意味着这个类型要拥有一个booleanValue属性,让它能够像一个布尔值一样被使用。这是不是表示我们要给每个Bird类型都添加这个属性呢?
  当然不需要,有更简单的方法。添加如下代码到Bird的定义中:

extension BooleanType where Self: Bird {
  var boolValue: Bool {
    return self.canFly
  }
}

这个extension会让canFly属性代表每个Bird类型的布尔值。
  为了测试一下效果,添加如下代码到playground的末尾:

if UnladenSwallow.African {
  print("I can fly!")
} else {
  print("Guess I’ll just sit here :[")
}

你应该看到“I can fly”出现在了辅助编辑器上。但更加值得注意的是,你刚刚在if条件语句中使用了African Unladen Swallow!

对Swift标准库的影响

你已经了解了protocol extensions在拓展自定义以及外部代码功能方面是多么好用。而Swift团队用protocol extensions来改善Swift标准库的方法可能会更加让你感到惊奇。
  Swift相当提倡函数式编程,在标准库中引入了很多范例,包括mapreducefilter等。这些方法被应用在很多CollectionType中,比如Array

// 计算数组中有多少个字符
["frog", "pants"].map { $0.length }.reduce(0) { $0 + $1 } // 返回 9

对数组调用map方法会返回另一个数组,而reduce则将结果归约成一个最终值9。
  在这个例子中,mapreduce都是Swift标准库的一部分。如果你按下cmd同时点击map,你可以看到具体的方法定义。
  在Swift 1.2中,定义看起来是这样的:

// Swift 1.2
extension Array : _ArrayType {
  /// 返回一个 `Array`, 其中包含调用的结果
  /// `transform(x)` 作用于每个`self`的`x`元素
  func map<U>(transform: (T) -> U) -> [U]
}

这表明在Swift 1.2中,map需要在每个Swift标准库的CollectionType中被重定义!这是因为即使ArrayRange都是CollectionType,结构体(struct)不能有子类,也不能有共享的方法实现。
  这不仅仅是Swift标准库的实现细节,这为我们使用Swift类型增加了限制。
下面的泛型方法接受一个FlyableCollectionType,返回其中airspeedVelocity最高的元素。

func topSpeed<T: CollectionType where T.GeneratorType: Flyable (collection: T) -> Double {
  collection.map { $0.airspeedVelocity }.reduce { max($0, $1)}
}

在Swift 1.2中,没有protocol extensions,这实际上会带来编译错误。mapreduce只存在于定义好的具体类型当中,对于任意的CollectionType是没有效果的。
  在Swift 2中,得益于protocol extensions,map的定义变成了如下:

// Swift 2.0
extension CollectionType {
  /// 返回一个 `Array`,包含把 `transform`映射到
  /// `self`上的结果.
  ///
  /// - 复杂度: O(N).
  func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]
}

即使你在Swift开源之前暂时不能看到源代码(译者注:现在已经开源),所有的CollectionType现在都拥有了map的默认实现!
  将下面之前提到过的泛型方法加入到playground中:

 func topSpeed<T: CollectionType where T.Generator.Element == Flyable>(c: T) -> Double {
  return c.map { $0.airspeedVelocity }.reduce(0) { max($0, $1) }
}

mapreduce方法现在可以用于你的Flyable集合实例了。把下面的代码加入playground,你现在终于可以回答它们之中谁是最快的了:

let flyingBirds: [Flyable] = 
  [UnladenSwallow.African,
  UnladenSwallow.European,
  SwiftBird(version: 2.0)]

topSpeed(flyingBirds) // 2000.0

结果毫无疑问,不是么?:]

下一步该做什么

可以在这里下载本教程完整的playground代码。
  通过搭建简单的protocols并用protocol extensions拓展它们,你已经见识过了POP的强大之处。使用默认实现,你可以让已有的protocols拥有简明且自动化的行为,类似基类但胜在能应用于struct和enum类型。
  更进一步的,protocol extensions不仅能扩展自定义的protocols,还可以扩展Swift标准库,Cocoa以及Cocoa Touch中的protocols。
  想要对Swift 2的新特性进行总览的话,可以阅读我们的what's new in Swift 2.0,或者移步官方博客Apple’s Swift blog
  Apple’s developer portal上有个很棒的WWDC视频推荐:Protocol Oriented Programming。想要深度了解POP理论的同学可以看看。
  如果还有什么问题,欢迎在原文以及这里的评论区进行讨论。

写在最后:本文中一些不影响文章理解的专业名词没有进行翻译,比如protocol和extensions,我尽量避免使用诸如“协议拓展”之类的字眼,一是避免和大家头脑中的中文版本产生偏差,二是对于这些词汇尽量标准化比较好。POP是翻译成“面向接口编程”还是“面向协议编程”纠结了很久,还是感觉“接口”用起来更容易被理解。第一次翻译技术文章,希望大家能提出一些建设性意见:-)

上一篇 下一篇

猜你喜欢

热点阅读