1. 概述
协议只提供方法的声明,不提供实现。协议可以被类、结构体和枚举实现。
2. 协议的语法 Protocol Syntax
定义一个协议:
protocol SomeProtocol { // protocol definition goes here }
如果一个类有父类并且需要遵守某个协议,将父类和协议写在冒号后面:
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol { // class definition goes here }
3. 属性的要求 Property Requirements
如果协议的属性需要实现get/set方法,可以如下实现:
protocol SomeProtocol { var mustBeSettable: Int { get set } // 既要实现set,又要实现get var doesNotNeedToBeSettable: Int { get } // 只要求实现get }
使用 class 关键字表明属性从属于类属性,使用 static 关键字表明属性从属于结构体属性和枚举属性:
protocol AnotherProtocol { class var someTypeProperty: Int { get set } }
下面定义了一个只有一个属性的协议:
protocol FullyNamed { var fullName: String { get } }
定义Person结构体实现上面的协议:
struct Person: FullyNamed { var fullName: String } let john = Person(fullName: "John Appleseed") // john.fullName is "John Appleseed"
因为Person中定义了一个String类型的Stored属性 fullName,也就是Person结构体完全符合了(conformed)FullyNamed协议。
下面定义了一个类,同样实现了(或者说符合、一致)FullyNamed协议:
class Starship: FullyNamed { var prefix: String? var name: String init(name: String, prefix: String? = nil) { self.name = name self.prefix = prefix } var fullName: String { return (prefix != nil ? prefix! + " " : "") + name } } var ncc1701 = Starship(name: "Enterprise", prefix: "USS") // ncc1701.fullName is "USS Enterprise"
4. 方法的要求 Method Requirements
协议中方法的参数不能有默认值。
在协议中,用 class 表示类方法,用 static 表示结构体方法和枚举方法(静态方法)。
protocol SomeProtocol { class func someTypeMethod() }
比如,在协议中定义一个实例方法:
protocol RandomNumberGenerator { func random() -> Double }
类 LinearCongruentialGenerator 实现了上面的协议:
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 } } let generator = LinearCongruentialGenerator() println("Here's a random number: (generator.random())") // prints "Here's a random number: 0.37464991998171" println("And another one: (generator.random())") // prints "And another one: 0.729023776863283"
5. 可变方法的要求 Mutating Method Requirements
在十、方法 Methods我们知道,在值类型(结构体和枚举)中,如果方法想要修改自己的实例,需要定义为 mutating 类型。
如果某个协议中的方法,需要修改实现了这个协议的实例的值,应该将方法定义为mutating 类型。那么,实现了这个协议的枚举和结构体可以修改自己的实例本身。
注意:在类中,不需要写mutating关键字,mutating关键字为值类型专用。
定义一个协议:
protocol Togglable {
mutating func toggle()
}
值类型
OnOffSwitch
需要实现这个协议:
enum OnOffSwitch: Togglable { case Off, On mutating func toggle() { switch self { case Off: self = On case On: self = Off } } } var lightSwitch = OnOffSwitch.Off lightSwitch.toggle() // lightSwitch is now equal to .On
6. 构造器的需求 Initializer Requirements
协议中可以定义构造器:
protocol SomeProtocol {
init(someParameter: Int)
}
某个类实现了指定了构造器的协议时,不管是Designated构造器还是Convinence构造器,都需要使用 required 关键字表示:
class SomeClass: SomeProtocol { required init(someParameter: Int) { // initializer implementation goes here } }
required 关键字确保了所有实现了协议的类/子类都要显示/隐式的实现构造器。具体参见十三、初始化 Initialization。
如果子类覆盖了父类的Designated构造器,而且需要实现一个协议中的required构造器,那么同时写上override和required关键字:
protocol SomeProtocol { init() } class SomeSuperClass { init() { // initializer implementation goes here } } class SomeSubClass: SomeSuperClass, SomeProtocol { // "required" from SomeProtocol conformance; "override" from SomeSuperClass required override init() { // initializer implementation goes here } }
7. Failable构造器的需求 Failable Initializer Requirements
协议中可以定义failable initializer requirements,详见十三、初始化 Initialization。
协议中定义了 failable initializer requirements,那么实现了协议的类型中对应的构造器可以使failable 也可以是 非failable的。
原话:A failable initializer requirement can be satisfied by a failable or nonfailable initializer on a conforming type. A nonfailable initializer requirement can be satisfied by a nonfailable initializer or an implicitly unwrapped failable initializer.
8. 协议作为一种类型 Protocols as Types
协议本身不实现任何功能,协议表明了一些代码将要完成的功能。
因为协议是一种类型,所以你可以在其他类型可以使用的地方使用协议:
- 1)作为函数、方法、构造器的参数类型和返回值类型。
- 2)作为常量constant、变量variable、或属性property的类型
- 3)作为数组、字典和其他容器类型的Items的类型
注意:因为协议是一种类型,所以和其他类型一样(比如Int, String, Double等)使用大写字母开头。
下面的例子将协议作为一种类型来使用:
protocol RandomNumberGenerator { func random() -> Double }
class Dice { let sides: Int // sides面的骰子 let generator: RandomNumberGenerator init(sides: Int, generator: RandomNumberGenerator) { self.sides = sides self.generator = generator } func roll() -> Int { return Int(generator.random() * Double(sides)) + 1 } }
因为generator属性是RandomNumberGenerator协议类型,所以可以将任何实现了RandomNumberGenerator协议的实例赋值给它,因为确保了它存在generator.random()方法。
创建一个6面的骰子:
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
}
}
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator()) for _ in 1...5 { println("Random dice roll is (d6.roll())") } // Random dice roll is 3 // Random dice roll is 5 // Random dice roll is 4 // Random dice roll is 5 // Random dice roll is 4
9. 代理 Delegation
代理是一种设计模式,它允许类和结构体将自己的一些任务交给(代理给)其他类型的实例完成。
下面的例子是在Control Flow.一节的Snakes and Ladders(蛇梯棋)。下面的版本采用一个Dice
实例掷筛子,实现DiceGame协议
和DiceGameDelegate 协议:
protocol DiceGame { var dice: Dice { get } func play() } protocol DiceGameDelegate { // 游戏进度 func gameDidStart(game: DiceGame) func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) func gameDidEnd(game: DiceGame) }
class SnakesAndLadders: DiceGame { let finalSquare = 25 let dice = Dice(sides: 6, generator: LinearCongruentialGenerator()) var square = 0 var board: [Int] init() { board = [Int](count: finalSquare + 1, repeatedValue: 0) board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02 board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08 } var delegate: DiceGameDelegate? func play() { square = 0 delegate?.gameDidStart(self) gameLoop: while square != finalSquare {// 标签表达式 let diceRoll = dice.roll() delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll) switch square + diceRoll { case finalSquare: break gameLoop case let newSquare where newSquare > finalSquare: continue gameLoop default: square += diceRoll square += board[square] } } delegate?.gameDidEnd(self) } }
因为 SnakesAndLadders 的delegate属性为可选值类型,所以一旦delegate为空时,delegate方法的调用会优雅的结束,而不是报错。如果delegate不为空,那么代理的方法会被调用,并且传入 SnakesAndLadders 实例作为参数。
下面定义了DiceGameTracker
:
class DiceGameTracker: DiceGameDelegate { var numberOfTurns = 0 func gameDidStart(game: DiceGame) { numberOfTurns = 0 if game is SnakesAndLadders { println("Started a new game of Snakes and Ladders") } println("The game is using a (game.dice.sides)-sided dice") } func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) { ++numberOfTurns println("Rolled a (diceRoll)") } func gameDidEnd(game: DiceGame) { println("The game lasted for (numberOfTurns) turns") } }
gameDidStart函数在游戏开始时设置numberOfTurns为0,它的参数类型为DiceGame而不是SnakesAndLadders ,所以它能访问DiceGame的内容。
gameDidStart通过传入的game参数访问dice属性,因为game是协议类型,所以只要传入实现了这个协议的实例就可以了,而不用管是什么游戏。
DiceGameTracker
运行结果如下:
let tracker = DiceGameTracker() let game = SnakesAndLadders() game.delegate = tracker game.play() // Started a new game of Snakes and Ladders // The game is using a 6-sided dice // Rolled a 3 // Rolled a 5 // Rolled a 4 // Rolled a 5 // The game lasted for 4 turns
10. 使用扩展增加协议 Adding Protocol Conformance with an Extension
可以使用扩展,给已经实现了某些协议的类型增加新的协议。即使你无法访问这个类型的源代码,也可以给它添加协议。比如下面的例子:
protocol TextRepresentable { func asText() -> String }
之前定义的Dice类可以增加上面的协议:
extension Dice: TextRepresentable { func asText() -> String { return "A (sides)-sided dice" } }
现在可以使用asText方法了:
let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator()) println(d12.asText()) // prints "A 12-sided dice"
同样,也可以给SnakesAndLadders扩展TextRepresentable 协议:
extension SnakesAndLadders: TextRepresentable { func asText() -> String { return "A game of Snakes and Ladders with (finalSquare) squares" } } println(game.asText()) // prints "A game of Snakes and Ladders with 25 squares"
如果某类型已经实现了协议中的方法,那么只需要使用extention进行声明就可以了:
struct Hamster { var name: String func asText() -> String { return "A hamster named (name)" } } extension Hamster: TextRepresentable {}
那么:
let simonTheHamster = Hamster(name: "Simon") let somethingTextRepresentable: TextRepresentable = simonTheHamster println(somethingTextRepresentable.asText()) // prints "A hamster named Simon"
11. 协议的集合 Collections of Protocol Types
协议像数组和字典一样,可以作为一种类型存储在集合中:
let things: [TextRepresentable] = [game, d12, simonTheHamster]
遍历协议数组:
for thing in things { println(thing.asText()) } // A game of Snakes and Ladders with 25 squares // A 12-sided dice // A hamster named Simon
注意thing的类型为TextRepresentable,而不是Dice
, DiceGame
, Hamster,即使他们的实例的实际类型是
。Dice
, DiceGame
, Hamster
因为thing的类型是TextRepresentable,定义了asText方法,所以调用thing.asText()是安全的。
12. 协议的继承
协议可以继承一个或多个其他协议以给协议增加新的需求,协议的继承与类类似,但是需要列出继承来的多个协议,通过逗号隔开。
protocol InheritingProtocol: SomeProtocol, AnotherProtocol { // protocol definition goes here }
下面定义了一个继承了协议TextRepresentable
的协议:
protocol PrettyTextRepresentable: TextRepresentable { func asPrettyText() -> String }
协议PrettyTextRepresentable继承了协议TextRepresentable ,任何实现了协议PrettyTextRepresentable的类型既要满足协议TextRepresentable的要求,也要满足协议PrettyTextRepresentable的要求。
那么:
extension SnakesAndLadders: PrettyTextRepresentable { func asPrettyText() -> String { var output = asText() + ": " // 调用了子协议中的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 } }
那么:
println(game.asPrettyText()) // A game of Snakes and Ladders with 25 squares: // ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
13. Class-Only 协议
你可以通过 class 关键字,限制一个协议只能被类类型实现,class关键字必须紧跟在冒号后:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol { // class-only protocol definition goes here }
14. 协议的组合 Protocol Composition
使用 protocol<SomeProtocol, AnotherProtocol>
形式将多个协议组合起来,成为一个单一的协议组合体(a single protocol composition requirement)。
比如:
protocol Named { var name: String { get } } protocol Aged { var age: Int { get } } struct Person: Named, Aged { var name: String var age: Int }
func wishHappyBirthday(celebrator: protocol<Named, Aged>) { println("Happy birthday (celebrator.name) - you're (celebrator.age)!") }
let birthdayPerson = Person(name: "Malcolm", age: 21) wishHappyBirthday(birthdayPerson) // prints "Happy birthday Malcolm - you're 21!"
上面的例子定义了一个函数 wishHappyBirthday,它只有一个参数celebrator,这个参数为协议组合体,这个参数的类型并不重要,它只是了Named和Aged两个协议的组合体。
注意:协议组合体并不是创建了一个新的协议类型,它只是创建了一个临时的组合了所有协议的组合体。
15. 检查协议的实现 Checking for Protocol Conformance
你可以使用十七、类型绑定 Type Casting介绍的 is 关键字检查协议的实现,as 关键字将协议绑定到其他类型,语法与十七章相同。
- 1)如果一个实例实现了协议,is 表达式返回 true,否则返回 false。
- 2)向下绑定操作符 as? 返回一个协议类型的可选值,如果实例没有实现协议,那么返回 nil。
- 3)向下绑定操作符 as 强制向下绑定到一个协议类型,如果绑定失败,会触发运行时错误。
例如:
@objc protocol HasArea { var area: Double { get } }
注意:
- 1)只有当协议被定义为 @objc 属性时,才能检查它是否被实现。在Using Swift with Cocoa and Objective-C.中介绍了 @objc 关键字用来表明某个协议会被用于Objective-C代码。即使你的代码不与Objective-C进行交互,如果你想检查协议是否被实现,就应该把它定义为 @objc 属性。
- 2)@objc 属性只能用于类类型,不能用于枚举和结构体类型。如果协议被定义为 @objc ,那么它只能用于类类型。
下面两个类都实现了HasArea协议:
class Circle: HasArea { let pi = 3.1415927 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
} }
下面一个类没有实现 HasArea 协议:
class Animal { var legs: Int init(legs: Int) {
self.legs = legs
} }
Circle
, Country
和 Animal
没有共同的基类,但是他们都是类类型,所以他们的实例类型都可以用 AnyObject 表示:
let objects: [AnyObject] = [ Circle(radius: 2.0), Country(area: 243_610), Animal(legs: 4) ]
检查数组元素是否实现了 HasArea 协议:
for object in objects { if let objectWithArea = object as? HasArea { println("Area is (objectWithArea.area)") } else { println("Something that doesn't have an area") } } // Area is 12.5663708 // Area is 243610.0 // Something that doesn't have an area
无论他们是否实现了HasArea 协议, as? 操作符都是返回可选值类型,这个可选值绑定为 objectWithArea 。注意类型绑定并没有改变数据类型,详见类型绑定一节。
16. 可选类型协议的要求 Optional Protocol Requirements
你可以给协议定义可选类型的需求(optional requirements),实现协议的类型可以不实现这个需求,使用 optional 关键字表示。
optional protocol requirement 可以被可选值链条调用。可选值链条参见十六、Optional Chaining。
在optional protocol requirement后写一个问号 ? ,比如someOptionalMethod?(someArgument)检查需求是否被实现。当 Optional property requirements 和 optional method requirements 被访问的时候,将永远返回一个合适的可选值,以表明可能没有被实现。
注意:
- 1)Optional protocol requirements 只能被定义为 @objc 属性。即使你不语Objective-C 代码进行交互,也要写上 @objc ,以表明你想指定 optional requirements。
- 2)@objc 属性只能用于类类型,不能用于枚举和结构体类型。如果协议被定义为 @objc ,那么它只能用于类类型。
下面的例子定义了一个使用外部数据源进行数量增长的协
@objc protocol CounterDataSource { optional func incrementForCount(count: Int) -> Int // optional method requirement optional var fixedIncrement: Int { get } // optional property requirement }
下面定义了一个类,它有一个 CounterDataSource 类型的可选值属性dataSource:
@objc 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 } } }
increment方法使用可选值链条尝试去调用incrementForCount
方法。
注意这里有两层可选值链条:
第一层中,dataSource可能为 nil ,所以dataSource后有个问号表明只有当dataSourse不为nil时才调用 incrementForCount。
第二层中,即使 dataSource 存在,也不能保证它实现了 incrementForCount 方法,因为这个方法是 optional requirement ,所以 incrementForCount 方法后还写了个问号。
因为 incrementForCount 方法有上面两个可能调用失败的原因,所以不管它的返回值是不是定义为可选值类型,它一定返回可选值类型。
下面定义了数据源类:
class ThreeSource: CounterDataSource { let fixedIncrement = 3 }
将ThreeSourse作为一个新的Counter实例的数据源:
var counter = Counter() counter.dataSource = ThreeSource() for _ in 1...4 { counter.increment() println(counter.count) } // 3 // 6 // 9 // 12
定义一个更复杂的数据源:
class TowardsZeroSource: CounterDataSource { func incrementForCount(count: Int) -> Int { if count == 0 { return 0 } else if count < 0 { return 1 } else { return -1 } } }
使用数据源:
counter.count = -4 counter.dataSource = TowardsZeroSource() for _ in 1...5 { counter.increment() println(counter.count) } // -3 // -2 // -1 // 0 // 0