Swift 2之协议式编程介绍「译」


  • 原文:Introducing Protocol-Oriented Programming in Swift 2
  • 作者:Erik Kerber
  • 译者:Chamchamben

Swift Bird brings speedy new features to Swift 2!


备注:此教程需要安装Xcode 7及Swift 2,此时两者均可能处于beta版本。您可以通过苹果开发者门户网站下载最新的beta版本。『译者注:原文发布于2015.6.25,当时Xcode 7及Swift 2尚处于beta版本』。

在WWDC2015,苹果发布了包含众多新语言特性的Swift 2,帮助开发者改善他们的编码方式。

其中,最让人兴奋的要属协议扩展(protocol extensions)语法。在Swift 1中,开发者可以扩展原有的class, struct和enum类型。现在,开发者可以对protocol进行扩展了。

刚看到protocol extensions时,大家可能认为这只是一个相对次要的功能,但实际上protocol extensions非常强大,强大到甚至可以改变开发者的编码方式!在这个教程中,你将会跟随我们一起探索如何使用protocol extensions语法,以及如何进行「协议式编程」

开始

在Xcode中,选择File\New\Playground...打开一个新的playground,并将playground命名为SwiftProtocols。由于本教程可以用在任意平台上,platform可以随意选择。点击Next,保存Playground。
创建成功后,输入下述代码:

protocol Bird {
    var name: String { get }
    var canFly: Bool { get }
}
protocol Flyable {
    var airspeedVelocity: Double { get }
}

这里我们定义了一个简单的protocol Bird,包含属性namecanFly,同时定义了一个Flyable协议,包含属性airSpeedVelocity

在没有protocol的时候,大家一般会将Flyable定义为基类,然后通过继承的方式定义Bird及其他可以「飞」的类,例如飞机。在这里我们需要明确一点,所有的事情都由protocol开始!

当你开始定义类型时,你将会逐步发现「协议式编程」是如何使整个系统更加灵活健壮的。

定义遵循protocol的类型

在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,同时遵循BirdFlyable两个protocol。其中,属性airspeedVelocity是通过闭包的方式计算返回的。同时,由于可以飞行,所以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
    
    var airspeedVelocity: Double { return 2000.0 }
}

在这里,企鹅Penguin同样属于鸟类,但是不能飞。啊哈~这就体现出不使用类继承的好处了,若使用了类继承的方式,则所有子类中的canFly属性都只能统一了!雨燕SwiftBird是很快的,所以airspeedVelocity属性返回了2000.0,一个非常快的飞行速度。

到此,相信你已经发现一些多余的代码了。每种类型的Bird都要重新定义canFlytrue还是false,尽管Flyable已经暗示了canFly = true

扩展协议,使其包含默认的实现

通过protocol extensions语法,开发者可以给一个protocol定义默认的执行方法。在Bird协议下,添加如下代码:

extension Bird where Self: Flyable {
    var canFly: Bool { return true }
}

在此,我们给协议Bird指定了一个默认的执行方法:「如果该类型包含了Flyable协议,则canFly返回true」。这样,所有Flyable的bird都不需要另外再明确声明canFly = true了。

Swift 1.2中介绍了where语法在if-let中的使用,而Swift 2则给我们带来了有条件地扩展protocol的能力。

接下来,在FlappyBirdSwiftBird结构体中删除let canFly = true语句,你会发现playground运行一切正常,因为,protocol extension已经帮你处理好canFly属性了。

为什么不使用基类

协议扩展(protocol extensions)及其默认执行方法看起来可能跟使用基类和抽象类(abstract type)相似,但Swift有几点优势:

  • 由于类型可以遵循多个protocol,因此可以被多个protocols的默认行为修饰。与多重继承的方式不同,协议扩展不会引入额外的属性及状态。
  • protocol可以被class、struct及enum遵循,而基类和多重继承均只能限制在类之间使用。

换句话说,协议扩展提供了让value类型也能用上默认执行方法,而不仅是类专有了。

按照上述步骤,相信开发者已经知道如何在struct中应用protocol了;接下来,我们尝试在enum类型中应用protocol。请在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!")
        }
    }
}

跟其他value类型一样,在enum中,开发者只需给协议中的属性定义正确的实现。由于UnladenSwallow同时遵循BirdFlyable,故canFlay属性不需要定义其实现,默认为true
至此,你还认为本教程中的airspeedVelocity不会包含一个蟒蛇的引用吗?『译者注:水平有限,此处翻译生硬,原文为「Did you really think this tutorial involving airspeedVelocity wouldn't include a Monty Python reference?:]」』

协议扩展的应用

对于开发者来说,协议扩展最常用于对现有协议的扩展,包括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 += 1
        }while (index != self.endIndex)
        
        return result
    }
}

在此,我们对CollectionType协议进行扩展,添加一个skip(_:)方法,可以把集合中下标能与x整除的元素去除,并返回处理后的集合。

由于CollectionType是一个被arraysdictionaries遵循的协议,所以对CollectionType进行扩展后,arraydictionary都可以使用skip(_:)方法了!在playground中,增加如下代码:

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

bunchaBirds.skip(3)

在此,我们定义了包含了多种鸟类的数组bunchaBirds。由于CollectionType协议扩展了skip(_:)方法,因此我们可以调用skip(_:)方法来对数组进行处理。

扩展你的协议

除了可以扩展Swift原生协议,给Swift原生协议定义默认实现也同样让人激动。

在playground中,把协议Bird修改为:

protocol Bird: BolleanType {

由于遵循了BooleanType协议,因此,在Bird协议中需要添加一个boolValue来表达bool值。这是不是意味着开发者必须在每个遵循Bird协议的类型下都给boolValue设计一个实现呢?

当然,你可以使用协议扩展的方式给boolValue设计一个默认实现。在playground的Bird下方增加如下代码:

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

这样扩展了协议之后,canFly属性就可以充当遵循Bird协议类型的boolValue了。

下面我们通过下述代码进行测试:

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

在此,你会看到I can fly!的打印。但尤其需要注意的是,在代码中,你不过在if语句后使用了类型,并未使用任何额外的判断语句!

协议扩展对Swift原生库的影响

到目前为止,相信你已经体会到协议扩展对开发者代码带来的扩展性。同时,协议扩展也給Swift原生库代码的编写带来了极大的扩展性。

通过遵循map, reduce, filter协议,Swift可以提升原生库的函数式编程模板。这些方法在CollectionType类型中得到不少体现,例如Array类型:

["frog", "pants"].map{$0.length}.reduce(0) { $0 + $1 }

上述代码中,让array调用map方法,得到一个新的array,然后让新array调用reduce方法,得到新array中的元素累加和9.

在此,mapreduce方法都成为了Array的原生方法。如果你按住Cmd并点击map,就可以看到map方法是如何定义的。

在Swift 1.2中,你将看到:

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

这里,map方法被定义为Array类型的扩展。但是,Swift的高阶函数并非只提供Array使用,应该所有的CollectionType都能够使用,那么在Swift 1.2中是如何实现的呢?

如果让Range调用map方法,你可以看到如下定义:

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

至此,我们发现在Swift 1.2中,因为struct不能成为子类,也不能包含公用实现,所以必须给所有CollectionType都重新定义map的实现。

这种方式不仅限制了Swift标准库,同时也限制了开发者对Swift中各种类型的使用方式。

下列函数形参为「遵循Flyable协议的CollectionType」,返回值为数值最高的airspeedVelocity

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

由于Swift 1.2没有协议扩展功能,上述代码实际上会引入编译错误。mapreduce只存在于一个具体的定义好的类型中,而非存在于抽象的CollectionType中。

但是,在Swift 2.0 协议扩展的功能下,map方法的定义变成了如下:

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

虽然不能看到看到方法map的源码 —— 至少等到Swift 2正式发布将会是开源的!现在,CollectionType有了一个默认的map实现,所有遵循CollectionType协议的类型都将支持map方法。

接下来,在playground中添加如下代码:

func topSpeed(c : T) -> Double {
    return c.map({$0.airspeedVelocity}).reduce(0) { max($0, $1) }
}

上述函数中,mapreduce方法均能被遵循CollectionType协议的类型调用。现在,通过下述代码,即可算出「哪种鸟类飞得最快」了:

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

topSpeed(flyingBirds)

接下来应该做什么

首先,你可以下载本篇教程中完整的playground代码。

通过本教程,相信你已经体会到「协议式编程」的威力。你可以自己定义简单的协议然后在使用时根据需要去扩展它,同时,你也可以给先有的协议编写默认的实现,类似于基类但不同于基类的,协议还能够支持structs和enums。

再者,协议扩展不仅可以扩展你自己写的协议,同时也可以扩展Swift标准库里面的协议,Cocoa及Cocoa Touch的协议。

如果想知道Swift 2中还有什么新功能,你可以通过我们的文章「what’s new in Swift 2.0」或者苹果官方博客「Apple’s Swift blog」中查看。

同时,你可以通过观看WWDC的「Protocol Oriented Programming」视频,深入了解「协议式编程」背后的理论。

如果有任何问题?欢迎留言讨论!

你可能感兴趣的:(Swift 2之协议式编程介绍「译」)