协议(下)
在拓展中添加协议成员
通过扩展使得Dice
类型遵循了一个新的协议,这和Dice
类型在定义的时候声明为遵循TextRepresentable
协议的效果相同。在扩展的时候,协议名称写在类型名之后,以冒号隔开,在大括号内写明新添加的协议内容。
protocol TextRepresentable { func asText() -> String } extension Dice: TextRepresentable { // 通过拓展让Dice类遵循TextRepresentable协议 func asText() -> String { // 实现协议方法 return "A (sides)-sides dice" } } let dice = Dice(sides: 6, generator: LinearCongruentialGenerator()) dice.asText() // "A 6-sides dice"
通过拓展补充协议声明
当一个类型已经实现了协议中的所有要求,却没有声明为遵循该协议时,可以通过扩展(空的扩展体)来补充协议声明:
protocol TextRepresentable { func asText() -> String } struct Hamster { var name: String // 实现了协议方法,但是没有遵循协议 func asText() -> String { return "A hamster named: (name)" } } extension Hamster: TextRepresentable {} // 可以通过拓展是结构体遵循协议,因为实现了协议方法,所以只要写个空拓展就好。 // 通过拓展方式使Hamster遵循TextRepresentable,现在可以使用协议名作为类型 let simonTheHamster: Hamster = Hamster(name: "Simon") let somethingTextRepresentable: TextRepresentable = simonTheHamster somethingTextRepresentable.asText() // "A hamster named: Simon"
集合中的协议类型
协议类型可以在集合中使用,表示元素都遵循了一个协议
let things: [TextRepresentable] = [simonTheHamster, somethingTextRepresentable] for thing in things { thing.asText() }
协议的继承
协议能够继承一个或多个其他协议,可以在继承的协议基础上增加新的内容要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
protocol TextRepresentable { func asText() -> String } protocol PrettyTextRepresentable: TextRepresentable { func asPrettyText() -> String }
例子
extension SnakesAndLadders: PrettyTextRepresentable { func asText() -> String { return "A game of Snakes and Ladders with (finalSquare) squares" } func asPrettyText() -> String { var output = asText() + ": " for index in 1...finalSquare { switch board[index] { case let ladder where ladder > 0: output += "↑ " case let snake where snake < 0: output += "↓ " default: output += "○ " } } return output } } print(game.asPrettyText())
类专属协议
你可以在协议的继承列表中,通过添加class
关键字,限制协议只能适配到类(class)类型。(结构体或枚举不能遵循该协议)。该class
关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。
protocol SomeInheritedProtocol { } protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol { // 只允许类遵循的协议 }
协议合成
有时候需要同时遵循多个协议。你可以将多个协议采用protocol<SomeProtocol, AnotherProtocol>
这样的格式进行组合,称为协议合成
。你可以在<>
中罗列任意多个你想要遵循的协议,以逗号分隔。
注意:协议合成
并不会生成一个新协议类型,而是将多个协议合成为一个临时的协议,超出范围后立即失效。
protocol Named: class { var name: String { get } } protocol Aged: class { var age: Int { get } } class Person: Aged, Named { let name: String let age: Int init(name: String, age: Int) { self.name = name self.age = age } } let p1 = Person(name: "Alex", age: 23) func wishHappyBirthday(celebrator: protocol<Named, Aged>) { // 参数类型规定要同时遵守两个协议的实例 print("Happy birthday (celebrator.name) - you're (celebrator.age)!") } wishHappyBirthday(p1) // "Happy birthday Alex - you're 23!"
检查协议的一致性
你可以使用is
和as
操作符来检查是否遵循某一协议或强制转化为某一类型。检查和转化的语法和之前相同。
is
操作符用来检查实例是否遵循
了某个协议
as?
返回一个可选值,当实例遵循
协议时,返回该协议类型;否则返回nil
as
用以强制向下转型,如果强转失败,会引起运行时错误。
protocol HasArea { var area: Double { get } } class Circle: HasArea { let pi = 3.1415926 var radius: Double var area: Double { return pi * radius * radius } init(radius: Double){ self.radius = radius } } class Country: HasArea { var area: Double init(area: Double){ self.area = area } } class Animal { // Animal没有继承HasArea var legs: Int init(legs: Int){ self.legs = legs } } let objects: [AnyObject] = [ Circle(radius: 2.0), Country(area: 200_000), Animal(legs: 4) ] for object in objects { if let objectWithArea = object as? HasArea { print("Area is (objectWithArea.area)") } else { print("Something that doesn't have an area") } }
对可选协议的规定
协议可以含有可选成员,其遵循者
可以选择是否实现这些成员。在协议中使用optional
关键字作为前缀来定义可选成员。
可选协议在调用时使用可选链
,因为协议的遵循者可能没有实现可选内容。
像someOptionalMethod?(someArgument)
这样,你可以在可选方法名称后加上?
来检查该方法是否被实现。可选方法和可选属性都会返回一个可选值(optional value)
,当其不可访问时,?
之后语句不会执行,并整体返回nil
注意:可选协议只能在含有@objc
前缀的协议中生效。且@objc
的协议只能被类
遵循这个前缀表示协议将暴露给Objective-C代码。即使你不打算和Objective-C有什么交互,如果你想要指明协议包含可选属性,那么还是要加上@obj
前缀
@objc protocol CounterDataSource { optional func incrementForCount(count: Int) -> Int optional var fixedIncrement: Int { get } } class Counter { var count = 0 var dataSource: CounterDataSource? func increment() { if let amount = dataSource?.incrementForCount?(count) { count += amount } else if let amount = dataSource?.fixedIncrement { count += amount } } } class ThreeSource: CounterDataSource { @objc var fixedIncrement: Int = 3 } var counter = Counter() counter.dataSource = ThreeSource() for _ in 1...4 { counter.increment() print(counter.count)
// 3 6 9 12 }
class TowardsZeroSource: CounterDataSource { @objc func incrementForCount(count: Int) -> Int { switch count { case 0: return 0 case let mCount where mCount > 0: return -1 default: return 1 } } } let counter = Counter() counter.count = -4 counter.dataSource = TowardsZeroSource() for _ in 1...5 { counter.increment() print(counter.count) // -3 -2 -1 0 0 }
协议扩展
使用扩展协议的方式可以为遵循者提供方法或属性的实现。通过这种方式,可以让你无需在每个遵循者中都实现一次,无需使用全局函数,你可以通过扩展协议的方式进行定义。
protocol RandomNumberGenerator { func random() -> Double } class LinearCongruentialGenerator: RandomNumberGenerator { var lastRandom = 42.0 let m = 139968.0 let a = 3877.0 let c = 29573.0 func random() -> Double { lastRandom = ((lastRandom * a + c) % m) return lastRandom / m } } extension RandomNumberGenerator { func randomBool() -> Bool { return random() > 0.5 } } let generator = LinearCongruentialGenerator() print("Here's a random number: (generator.random())") // Here's a random number: 0.37464991998171 print("And here's a random Boolean: (generator.randomBool())") // And here's a random Boolean: true
提供默认实现
可以通过协议扩展的方式来为协议规定的属性和方法提供默认的实现。如果协议的遵循者对规定的属性和方法提供了自己的实现,那么遵循者提供的实现将被使用。
注意:通过扩展协议提供的协议实现和可选协议规定有区别。虽然协议遵循者无需自己实现,通过扩展提供的默认实现,可以不是用可选链调用。
protocol TextRepresentable { func asText() -> String } protocol PrettyTextRepresentable: TextRepresentable { func asPrettyText() -> String var someProperty: String { get } } extension PrettyTextRepresentable { func asPrettyText() -> String { return asText() + " some text" } var someProperty: String { return "" } } class SomeClass: PrettyTextRepresentable { var name = "Tom" func asText() -> String { return name } } let person = SomeClass() print(person.asPrettyText()) print(person.someProperty)
为协议扩展添加限制条件
在扩展协议的时候,可以指定一些限制,只有满足这些限制的协议遵循者,才能获得协议扩展提供的属性和方法。这些限制写在协议名之后,使用where
关键字来描述限制情况。
protocol TextRepresentable { func asText() -> String } struct Hamster: TextRepresentable { var name: String func asText() -> String { return "A hamster named (name)" } } // 拓展 CollectionType 协议,当元素遵循 TextRepresentable 协议时,可用拓展中的功能 extension CollectionType where Generator.Element : TextRepresentable { func asList() -> String { return "some string" } } let murrayTheHamster = Hamster(name: "Murray") let morganTheHamster = Hamster(name: "Morgan") let mauriceTheHamster = Hamster(name: "Maurice") let hamsters = [murrayTheHamster, morganTheHamster, mauriceTheHamster] // 因为Array遵循CollectionType协议,数组的元素又遵循TextRepresentable协议,所以数组可以使用asList()方法得到数组内容的文本表示 print(hamsters.asList())