/*协议定义了一个蓝图,规定了用来实现某一特定工作或者功能所必需的方法和属性。类,结构体或枚举类型都可以遵循协议,
并提供具体实现来完成协议定义的方法和功能。任意能够满足协议要求的类型被称为遵循(conform)这个协议。
协议的语法
协议的定义方式与类,结构体,枚举的定义非常相似。
protocol SomeProtocol {
// 协议内容
}
要使类遵循某个协议,需要在类型名称后加上协议名称,中间以冒号:分隔,作为类型定义的一部分。遵循多个协议时,各协议之间用逗号,分隔。
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 结构体内容
}
如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔。
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 类的内容
}
对属性的规定
协议可以规定其遵循者提供特定名称和类型的实例属性(instance property)或类属性(type property),而不指定是存储型属性(stored property)还是计算型属性(calculate property)。此外还必须指明是只读的还是可读可写的。
如果协议规定属性是可读可写的,那么这个属性不能是常量或只读的计算属性。如果协议只要求属性是只读的(gettable),那个属性不仅可以是只读的,如果你代码需要的话,也可以是可写的。
协议中的通常用var来声明属性,在类型声明后加上{ set get }来表示属性是可读可写的,只读属性则用{ get }来表示。
protocol SomeProtocol {
var mustBeSettable : Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
在协议中定义类属性(type property)时,总是使用static关键字作为前缀。当协议的遵循者是类时,可以使用class或static关键字来声明类属性,但是在协议的定义中,仍然要使用static关键字。
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
*/
protocol FullyNamed {
var fullName:String { get }
}
//遵循FullyNamed协议,有一个只读的fullName属性
struct Person: FullyNamed {
var fullName:String
}
let john = Person(fullName: "John Appleseed")
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 sp = Starship(name: "Enterprise", prefix: "USS")
print("sp.fullName is \(sp.fullName)")
//sp.fullName is USS Enterprise
/*协议中声明方法
协议可以要求其遵循者实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通的方法一样放在协议的定义中,
但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是在协议的方法定义中,不支持参数默认值。
protocol SomeProtocol {
//类型方法
static func someTypeMethod()
}
*/
//随机数生成器
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
}
}
let generator = LinearCongruentialGenerator()
print("Here's a random number: \(generator.random())")
//Here's a random number: 0.37464991998171
print("And another one: \(generator.random())")
//And another one: 0.729023776863283
/*对Mutating方法的规定
有时候需要在方法中修改它所属的实例及其所属实例属性的值,那么在定义协议方法时,需要在func 前加上 mutating关键字
注意:用类实现协议中的mutating方法时,不用写mutating关键字;用结构体,枚举实现协议中的mutating方法时,必须写mutating关键字。
*/
//可切换的协议
protocol Togglable {
// 切换方法
mutating func toggle()
}
enum OnOffSwitch: Togglable {
case Off, On
mutating func toggle() {
switch self {
case Off:
self = On
case On:
self = Off
}
}
}
var lightSwitch = OnOffSwitch.On
lightSwitch.toggle()
print(lightSwitch) //Off
/*对构造器对规定
协议可以要求它的遵循者实现指定的构造器。你可以像书写普通的构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
protocol SomeProtocol {
init(someParameter: Int)
}
协议构造器规定在类中的实现
你可以在遵循该协议的类中实现构造器,并指定其为类的指定构造器(designated initializer)或者便利构造器(convenience initializer)。
但在这两种情况下,你都必须给构造器实现标上"required"修饰符:
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
//构造器实现
}
}
使用required修饰符可以保证:所有的遵循该协议的子类,同样能为构造器规定提供一个显式的实现或继承实现。
注意:如果类已经被标记为final,那么不需要在协议构造器的实现中使用required修饰符。因为final类不能有子类。
*/
//如果一个子类重写了父类的指定构造器,并且该构造器遵循了某个协议的规定,那么该构造器的实现需要被同时标示required和override修饰符
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
//构造器实现
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
// 因为遵循协议,需要加上"required";因为继承自父类,需要加上"override"
required override init() {
//构造器实现
}
}
/*可失败构造器的规定
可以通过给协议Protocols中添加可失败构造器来使遵循该协议的类型必须实现该可失败构造器。
如果在协议中定义一个可失败构造器,则在遵顼该协议的类型中必须添加同名同参数的可失败构造器或非可失败构造器。
如果在协议中定义一个非可失败构造器,则在遵循该协议的类型中必须添加同名同参数的非可失败构造器或隐式解析类型的可失败构造器(init!)。
*/
protocol FailProtocol {
//可失败构造器
init?(type: String)
//非可失败构造器
init(type:String, name:String)
}
class FailClass: FailProtocol {
//实现为非可失败
required init(type: String) {
}
//实现为隐士解析可选
required init!(type: String, name: String) {
}
}
/*尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。
协议可以像其他普通类型一样使用,使用场景:
作为函数、方法或构造器中的参数类型或返回值类型
作为常量、变量或属性的类型
作为数组、字典或其他容器中的元素类型
注意
协议是一种类型,因此协议类型的名称应与其他类型(Int,Double,String)的写法相同,
使用大写字母开头的驼峰式写法,例如(FullyNamed和RandomNumberGenerator)
*/
//定义骰子类
class Dice {
//骰子的面数
let sides: Int
//协议作为一种类型
let generator: RandomNumberGenerator
init(sides:Int, generator: RandomNumberGenerator) {
self.sides = sides
self.generator = generator
}
//骰子滚动的点数
func roll() -> Int {
return Int(generator.random() * Double(sides)) + 1
}
}
var d6 = Dice(sides: 6, generator: LinearCongruentialGenerator())
for _ in 1...5 {
print("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
/*委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能交由(委托)给其他的类型的实例。
委托模式的实现很简单: 定义协议来封装那些需要被委托的函数和方法, 使其遵循者拥有这些被委托的函数和方法。
委托模式可以用来响应特定的动作或接收外部数据源提供的数据,而无需要知道外部数据源的类型信息。
*/
//骰子游戏定义
protocol DiceGame {
var dice: Dice { get }
func play()
}
//骰子游戏代理协议
protocol DiceGameDelegate {
func gameDidStart(game: DiceGame)
func game(game: DiceGame, didStartNewTurnWidthDiceRoll 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, didStartNewTurnWidthDiceRoll: 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)
}
}
class DiceGameTracker: DiceGameDelegate {
var numberOfTurns = 0
func gameDidStart(game: DiceGame) {
numberOfTurns = 0
if game is SnakesAndLadders {
print("Started a new game of Snakes and Ladders")
}
print("The game is suing a \(game.dice.sides) -sided dice")
}
func game(game: DiceGame, didStartNewTurnWidthDiceRoll diceRoll: Int) {
++numberOfTurns
print("Rolled a \(diceRoll)")
}
func gameDidEnd(game: DiceGame) {
print("The game lasted for \(numberOfTurns) turns")
}
}
let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()
//Started a new game of Snakes and Ladders
//The game is suing a 6 -sided dice
//Rolled a 3
//Rolled a 5
//Rolled a 4
//Rolled a 5
//Rolled a 4
//Rolled a 1
//Rolled a 4
//Rolled a 2
//Rolled a 1
//The game lasted for 9 turns
/*在扩展中添加协议成员
即便无法修改源代码,依然可以通过扩展(Extension)来扩充已存在类型(译者注: 类,结构体,枚举等)。
扩展可以为已存在的类型添加属性,方法,下标脚本,协议等成员。
注意
通过扩展为已存在的类型遵循协议时,该类型的所有实例也会随之添加协议中的方法
*/
//可被描述为文本
protocol TextRepresentable {
var textualDescription: String { get }
}
//Dice遵循了新的协议
extension Dice: TextRepresentable {
var textualDescription: String {
return "A \(sides)-sided dice"
}
}
let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())
print(d12.textualDescription)
//A 12-sided dice
//蛇和梯子游戏类遵循协议
extension SnakesAndLadders: TextRepresentable {
var textualDescription: String {
return "A game of Snakes and Ladders width \(finalSquare) squares"
}
}
/*通过扩展补充协议声明
当一个类型已经实现了协议中的所有要求,却没有声明为遵循该协议时,可以通过扩展(空的扩展体)来补充协议声明:
注意:
即使满足了协议的所有要求,类型也不会自动转变,因此你必须为它做出显式的协议声明
*/
//仓鼠类
struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named \(name)"
}
}
extension Hamster: TextRepresentable{}
let simonTheHamster = Hamster(name: "Simon")
let somethingTextRepresentable: TextRepresentable = simonTheHamster
print(somethingTextRepresentable.textualDescription)
//A hamster named Simon
/*集合中的协议类型
协议类型可以在集合使用,表示集合中的元素均为协议类型
*/
let things: [TextRepresentable] = [game, d12, simonTheHamster]
for thing in things {
print(thing.textualDescription)
}
//A game of Snakes and Ladders width 25 squares
//A 12-sided dice
//A hamster named Simon
/*协议的继承
协议能够继承一个或多个其他协议,可以在继承的协议基础上增加新的内容要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
//协议定义
}
*/
//PrettyTextRepresentablexi继承TextRepresentable,任何遵循PrettyTextRepresentable协议的类型在满足该协议的要求时,也必须满足TextRepresentable协议的要求。
protocol PrettyTextRepresentable: TextRepresentable {
var prettyTextualDescription: String { get }
}
extension SnakesAndLadders: PrettyTextRepresentable {
var prettyTextualDescription: String {
//SnakesAndLadders上文已经被扩展为TextRepresentable,所以可以直接调用textualDescription
var output = textualDescription + ":\n"
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.prettyTextualDescription)
//◇◇▲◇◇▲◇◇▲▲◇◇◇▼◇◇◇◇▼◇◇▼◇▼◇
/*类专属协议
你可以在协议的继承列表中,通过添加class关键字,限制协议只能适配到类(class)类型。
(结构体或枚举不能遵循该协议)。该class关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// class-only protocol definition goes here
}
在以上例子中,协议SomeClassOnlyProtocol只能被类(class)类型适配。如果尝试让结构体或枚举类型适配该协议,则会出现编译错误。
注意
当协议想要定义的行为,要求(或假设)它的遵循类型必须是引用语义而非值语义时,应该采用类专属协议。
*/
//定义为类专属协议
protocol Fish:class {
var name :String {get}
}
//必须是类类型才能遵循此协议
class SmallFish:Fish {
var name:String = ""
}
/*协议合成
有时候需要同时遵循多个协议。你可以将多个协议采用protocol<SomeProtocol, AnotherProtocol>这样的格式进行组合,
称为协议合成(protocol composition)。你可以在<>中罗列任意多个你想要遵循的协议,以逗号分隔。
注意:
协议合成并不会生成一个新协议类型,而是将多个协议合成为一个临时的协议,超出范围后立即失效。
*/
protocol Named {
var name:String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person00:Named, Aged
{
var name: String
var age: Int
}
func wishHappyBirthday(celebrator: protocol<Named, Aged>) {
print("Happy birthday \(celebrator.name)-you're \(celebrator.age)")
}
let birthdayPerson = Person00(name: "Mali", age: 21)
//Person00实现了两个协议
wishHappyBirthday(birthdayPerson)
//Happy birthday Mali-you're 21
/*检查协议的一致性
你可以使用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 {
var legs:Int
init(legs:Int) {self.legs = legs}
}
let objects:[AnyObject] = [
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
for object in objects {
if let objectWidthArea = object as? HasArea {
print("Area is \(objectWidthArea.area)")
} else {
print("Something that doesn't have an area")
}
}
//Area is 12.5663704
//Area is 243610.0
//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:NSObject, CounterDataSource {
let fixedIncrement = 3
}
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
counter.increment()
print("\(counter.count)")
}
//3
//6
//9
//12
//类只有继承了NSObject才能加@objc
@objc class TowardsZeroSource: NSObject,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()
print(counter.count)
}
//-3
//-2
//-1
//0
//0
/*协议扩展
使用扩展协议的方式可以为遵循者提供方法或属性的实现。通过这种方式,可以让你无需在每个遵循者中都实现一次,也无需使用全局函数。
*/
extension RandomNumberGenerator {
func randomBool() -> Bool {
return random() > 0.5
}
}
let generator1 = LinearCongruentialGenerator()
print("Here's random number: \(generator1.random())")
//Here's random number: 0.37464991998171
print("And here's a random Boolean: \(generator1.randomBool())")
//And here's a random Boolean: true
/*提供默认实现
可以通过协议扩展的方式来为协议规定的属性和方法提供默认的实现。如果协议的遵循者对规定的属性和方法提供了自己的实现,
那么遵循者提供的实现将被使用。
注意
通过扩展协议提供的协议实现和可选协议规定有区别。虽然协议遵循者无需提供自己的实现,通过扩展提供的默认实现,可以不用可选链调用。
*/
extension PrettyTextRepresentable {
var prettyTextualDescription: String {
return textualDescription
}
}
/*为协议扩展添加限制条件
在扩展协议的时候,可以指定一些限制,只有满足这些限制的协议遵循者,才能获得协议扩展提供的属性和方法。
这些限制写在协议名之后,使用where关键字来描述限制情况。(Where语句)。:
例如,你可以扩展CollectionType(集合类型)协议,但是只适用于元素遵循TextRepresentable的情况:
*/
extension CollectionType where Generator.Element : TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map{ $0.textualDescription}
return "["+itemsAsText.joinWithSeparator(", ") + "]"
}
}
//Hamster,它遵循TextRepresentable协议:
let murrayTheHamster = Hamster(name: "Murray")
let morganTheHamster = Hamster(name: "Morgan")
let mauriceTheMaster = Hamster(name: "Maurice")
let hamsters = [murrayTheHamster, morganTheHamster, mauriceTheMaster]
//因为Array遵循CollectionType协议,数组的元素又遵循TextRepresentable协议,
//所以数组可以使用textualDescription属性得到数组内容的文本表示:
print(hamsters.textualDescription)
//[A hamster named Murray, A hamster named Morgan, A hamster named Maurice]