返回顶部

OutOfMemory.CN技术专栏-> Swift-> Swift2面向协议编程

Swift2面向协议编程

更多

注意:这套教程要求Xcode 7、Swift 2,准备好了这个环境,下面的教程才能进行下去。你能够在 Apple’s developer portal 下载最新的beta版本。

在WWDC 2015上,Apple发布Swift语言的第二个修订版本–Swift 2,它包含了许多新的语言特性,这些特性能够帮助你更加方便得写代码。

在这些新特性中,最令我兴奋的是 protocol extensions 。在第一个版本的Swift中,它能够扩展class、struct和enum类型的功能。现在,在Swift 2中,你也能扩展一个协议。

首先,它可能听起来像是一个不太重要的特性,但是协议扩展(protocol extensions)它的确十分强大,并且能够改变你写代码的方式。在这篇教程中,你能探索创建和使用协议扩展的方式,而且它作为一个新的技术和面向协议编程的模式,将会开始出现在你的代码中。

你也能看到,Swift团队怎样使用协议扩展去增强他们自己的Swift的标准库的,并且看到它将会怎样影响你写代码的。

Getting Started

首先新建一个playground。在Xcode中,选择 File\New\Playground… ,然后命名这个playground为 SwiftProtocols 。你能选择任何平台,因为在本教程中的所有得代码都是平台无关的。点击 Next ,选择保存路径,最后点击 Create

创建好了playground后,添加如下代码:

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

protocol Flyable {
  var airspeedVelocity: Double { get }
}

这个定义了一个简单得 Bird 协议,其有一个属性 name canFly ,同样 Flyable 协议定义了一个 airspeedVelocity 属性。

在原先对协议的理解中,你可能已经开始使用Flayable作为一个基本类型了,并且通过继承定义Bird和其他能够飞的东西,例如飞机。但是现在,everything将开始作为协议出现。(原文:In a pre-protocol world, you might have started with Flyable as a base class and then relied on inheritance to define Bird as well as other things that fly, such as airplanes. Note that here, everything is starting out as a protocol!)

接下来,在你定义一个真实类型的时候,你将看到通过协议扩展怎样使你的整个系统更加的灵活。

Defining Protocol-conforming Types 定义一个遵守协议的类型

在playground的底部添加如下的struct定义:

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

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

这个定义了一个新的结构体: FlappyBird ,它实现了 Bird Flyable 协议。它得 airspeedVelocity 通过 flappyFrequency flappyAmplitude 计算获得的。因为能够飞,所以它的 canFly 返回true。

接下来,在playground的底部添加如下的两个结构体定义:

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 }
}

Penguin Bird ,但是不能飞。你不能继承所有得特性,毕竟不能使所有得鸟会飞。 SwiftBird 确实有很快得速度。

你已经看到一些冗余的地方,每个Bird类型,都必须声明它是否canFly,即使你已经在你的系统中表明它遵守Flyalbe协议了。

Extending Protocols With Default Implementations 使用默认实现扩展协议

使用协议扩展,你能够为协议定义默认的行为。在 Brid 定义的下面添加如下的代码:

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

这个定义了 Bird 的协议扩展,当类型也是 Flyable 的时候,使 canFly 的默认行为是返回true。换句话说,任意的Flyable bird不需要明确的声明 canFly 属性了。
img

Swift 1.2中介绍了在if-let绑定中的where的语法,在Swift 2使其更加的强大————把这个能力扩展到了协议上。

FlappyBird SwiftBird 结构体声明中删除了 let canFly = true 。你能够看到playground能够正常得编译了,由于协议扩展现在为你处理了其他得必要的工作。

Why Not Base Classes?

协议扩展和默认实现,可能看起来和其他语言中使用基类或者虚类相似,但是在Swift中,它提供了一些关键的有点。

  • 由于类型能够实现多于一个协议,他们能够被多个不同协议的默认实现装饰。与其他语言中的类的多重继承不同,协议扩展不去声明任何附加的状态。
  • 协议能够被classes、structs和enums遵守。基类和继承只能限制在类上使用。

换句话说,协议扩展为值类型和类提供了一种定义默认行为的能力。

你已经在struct中看到这些了。接下来,在playground的末尾添加如下的enum的定义。

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 遵守两个协议一样。因为她遵守 Bird Flyable 两个协议,所以它也获得canFly的默认行为。

Did you really think this tutorial involving airspeedVelocity wouldn’t include a Monty Python reference? :](译者注:不清楚怎么翻译)

Extending Protocols 扩展协议

也许使用协议扩展最普通的就是扩展现在的协议,在Swift中这些定义不仅存在于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 协议上定义了一个扩展,在其中定义了一个**skip(_:)方法,该方法跳过每个是skip(译者注:传递进入方法的参数)整数倍位置的元素,并且返回没有跳过元素的数组。

在Swift中, CollectionType 是一个呗arrays和dictionaries遵守的协议。这也就意味着,在你的app中这个新的行为已经在你的每个 CollectionType 上存在了。在playground的底部添加如下代码,看看会发生什么:

let bunchaBirds: [Bird] =
  [UnladenSwallow.African,
   UnladenSwallow.European,
   UnladenSwallow.Unknown,
   Penguin(name: "King Penguin"),
   SwiftBird(version: 2.0),
   FlappyBird(name: "Felipe", flappyAmplitude: 3.0, flappyFrequency: 20.0)]

bunchaBirds.skip(3)

这里,你定义了一个bird数组,包含你定义的多种类型。由于arrays遵守 CollectionType 协议,意味着 skip(_:) 在它上面可以使用。

Extending Your Own Protocols 扩展你自己的协议

与向Swift标准库的协议添加新的行为让你很兴奋了一样,你也能定义默认的行为。

修改 Bird 协议的声明,使其遵守 BooleanType 协议:

protocol Bird: BooleanType {

遵守 BooleanType 协议,意味着你的类型需要有一个 boolValue 属性,它是Boolean类型的。这个以为你现在必须为每个 Bird 类型添加这个属性吗?

当然不用,使用协议扩展就是一种简单得方式。在 Bird 定义的下面添加如下代码:

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

这个扩展,使用canFly属性代表每个 Bird 类型的布尔值。

实验一下,在playground中添加如下的代码:

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

Effects on the Swift Standard Library 在Swift标准库上的作用

你已经看到了协议扩展是定制和扩展你APP代码的一个强大的方式。当你看到Swift团队怎样使用协议扩展增强Swift标准库,你可能会更惊奇。

Swift有些地方有函数式编程的影子,例如标准库中的 map reduce filter 。这些方法存在于各个 CollectionType 的方法中,例如 Array

// Counts the number of characters in the array
["frog", "pants"].map { $0.length }.reduce(0) { $0 + $1 } // returns 9

在array上调用 map ,然后返回一个新的array,在新的数组上调用 reduce ,返回最后的值9。

译者注:reduce(sequence, initial, combineClosure): 从第一个初始值开始对其进行combineClosure操作,递归式地将序列中的元素合并为一个元素。

在这里, map reduce 作为Swift标准库中的Array的方法。如果你在 map Cmd-Click ,你能看到它是怎么定义的。

译者注:Cmd-Click 按住Command,点击鼠标左键。

在Swift 1.2中,你可能看到类似如下的定义:

// Swift 1.2
extension Array : _ArrayType {
  /// Return an `Array` containing the results of calling
  /// `transform(x)` on each element `x` of `self`
  func map<U>(transform: (T) -> U) -> [U]
}

map 函数是在定义在一个函数的扩展中。然而Swift的功能函数应该不仅仅能够作用在 Array 上,也能作用在任何 CollectionType 上,Swift 1.2是怎样使它这样的呢?

如果你在 Range 上调用 map 函数,在 map Cmd-Click ,你将看到如下的定义:

// Swift 1.2
extension Range {
  /// Return an array containing the results of calling
  /// `transform(x)` on each element `x` of `self`.
  func map<U>(transform: (T) -> U) -> [U]
}

它在Swift 1.2中出现, map 需要在Swift标准库中的任何一中 CollectionType 中重新定义。这是因为虽然 Array Range 都是 CollectionType 类型的,但是structs不能被子类化、没有统一的实现。

这个并不是Swift标准库制造的细微差别,这个限制也会作用在你使用Swift类型上。

这个泛型函数接收一个 Flyable 类型的 CollectionType 参数,返回 airspeedVelocity 的最高值:

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

在Swift 1.2中,如果没有协议扩展,这个将会出现编译错误。 map reduce 函数只是存在于Swift预定义的类型中,它并不会工作在任何 CollectionType 的子类型上。

在Swift 2.0中,存在协议扩展,在 Array Range 类型上定义了 map 方法,如下:

// Swift 2.0
extension CollectionType {
  /// Return an `Array` containing the results of mapping `transform`
  /// over `self`.
  ///
  /// - Complexity: O(N).
  func map<T>(@noescape transform: (Self.Generator.Element) -> T) -> [T]
}

尽管你不能看到 map 的源码,————至少在Swift 2开源以前看不到!———— CollectionType 现在有了一个 map 的默认实现,并且在所有的 CollectionType 上都能使用。

在你的playground的底部,添加如下的泛型函数:

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

现在 map reduce 在你的 Flyable 实例的collection中是可以使用了。现在你能通过在playground的底部添加如下的代码,响应它们中谁最快的问题了:

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

topSpeed(flyingBirds) // 2000.0

Where To Go From Here? 接下来去哪?

你能在 这里 下载本教程中的代码的playground。

通过创建你自己的简单的协议和使用协议扩展扩展它们。通过默认实现,你能给目前的协议提供通用的自动的行为,这个类似class,但是比它更好,因为它能够作用在结构体和枚举上。

此外,协议扩展不仅仅能够用于扩展你自己的协议,也能够扩展Swift标准库、Cocoa和Cocoa Touch中的协议,提供默认的实现。

为了对Swift 2的其他令人兴奋的特性有一个大概了解,你能够阅读 our “what’s new in Swift 2.0” article ,或者在 Apple’s Swift blog 查看Swift 2的介绍。

你能在苹果开发者门户网站中查看WWDC session Protocol Oriented Programming ,获取在它背后更深的原理。

有什么问题吗?在下面的讨论板块让我们知道吧!

推荐阅读:
支持

0

反对

0

MrSimple:高质量技术文章的聚合网站

您可以通过下面的社交媒体联系/了解作者的更多信息:

发表评论