// 通过扩展添加协议一致性
// 即便无法修改源代码, 依然可以通过扩展 令已有类型遵循并符合协议, 扩展可以为已有类型添加属性, 方法, 下标 以及构造器, 因此可以符合协议中的相应要求
// 注意: 通过扩展令已有类型遵循并符合协议时, 该类型的所有势力也会随之获得协议中定义的各项功能
protocol TextRepresentbble{
var textualDescription: String { get }
}
// 可以通过扩展, 令已有的类 Dice 遵循并符合 TextRepresentable 协议:
// extension Dice: TextOutputStream{
// var textualDescription: String{
// return "ddddd"
// }
// }
// 通过扩展遵循并符合协议, 和在原始定义中遵循并符合协议的效果完全相同, 协议名称写在类型名之后, 以冒号隔开, 然后在扩展的大括号内实现协议要求的内容
// 通过扩展遵循协议
// 当一个类型已经符合某个协议中的所有要求, 却还没有声明遵循该协议时, 可以通过空扩展体来遵循该协议:
struct Hamster{
var name: String
var textualDescription: String {
return "A hamster named \(name)"
}
}
extension Hamster: TextRepresentbble{}
// 从现在起, Hamster 的实例可以作为 TextRepresentable 类型使用
let simonTheHamster = Hamster.init(name: "Simon")
let somethingTextRepresentable: TextRepresentbble = simonTheHamster
print(somethingTextRepresentable.textualDescription)
// 注意: 即使满足了协议的所有要求, 类型也不会自动遵循协议, 必须显式地遵循协议
// 协议类型的集合
// 协议类型可以在数组或字典这样的集合中使用
let things: [TextRepresentbble] = [simonTheHamster]
// 如下所示, 可以便利 things 数组, 并打印每个元素的文本信息
for thing in things {
print(thing.textualDescription)
}
// thing 是 TextRepresentbble 类型而不是 Hamster 类型, 即使实例在幕后确实是这些类型中的一种, 由于 thing 是 TextRepresentbble 类型, 任何 TextRepresentbble 的实例都有一个 textualDescription 属性, 所以每次循环中可以安全地反问 thing.textualDescription
// 协议的继承
// 协议能够继承 一个 或 多个其他协议, 可以在继承 的协议的基础上增加新的要求, 协议的继承语法与 类的继承很相似, 多个被继承的协议之间用 (,) 分割
// protocol InheritingProtocol: SomeProtocol, AnotherProtocl{
// 这里是协议的定义部分
// }
// 如下例所示:
protocol PrettyTextRepresentable: TextRepresentbble{
var prettyTextualDescription: String { get }
}
// 例子中定义了一个新的协议 PrettyTextRepresentable, 它继承自 TextRepresentable 协议, 任何遵循 PrettyTextRepresentable 洗衣的类型在满足该协议的要求时, 也必须满足 TextRepresentbble 协议的要求.
//extension SnakesAndLadders: PrettyTextRepresentable {
// var prettyTextualDescription: String {
// 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
// }
//}
//上述扩展令 SnakesAndLadders 遵循了 PrettyTextRepresentable 协议,并提供了协议要求的 prettyTextualDescription 属性。每个 PrettyTextRepresentable 类型同时也是 TextRepresentable 类型,所以在 prettyTextualDescription 的实现中,可以访问 textualDescription 属性。
// 类类型专属协议
// 你可以在协议的继承列表中, 通过添加 class 关键字来限制协议只能被 类类型遵循, 而结构体 或枚举不能遵循该协议, class 关键字必须第一个出现在协议的继承列表中, 在其他继承的协议之前:
// protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 这里是类类型专属协议的定义部分
// }
// 在以上例子中,协议 SomeClassOnlyProtocol 只能被类类型遵循, 如果尝试让结构体或枚举 类型遵循该协议,则会导致编译错误
// 注意: 当协议定义的要求 需要遵循协议的 类型必须是 引用语义 而非 值语义, 应该采用类类型专属协议
// 协议合成
// 有时候需要同时遵循多个协议, 你可以将多个协议采用 SomeProtocol & AntherProtocol 这样的格式进行组合, 称为 协议合成. 你可以罗列任意多个你想要遵循的协议, 以与符号 (&) 分割
protocol Named{
var name: String{ get }
}
protocol Aged{
var age: Int { get }
}
struct Person: Named,Aged{
var name: String
var age: Int
}
func wishHappyBirthday(to celebrator: Named & Aged){
print("Happy birthday , \(celebrator.name), you're \(celebrator.age)")
}
let birthdayPerson = Person.init(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// Named 协议包含 String 类型的 name 属性, Aged 协议包含 Int 类型的 age 属性, person 结构体遵循了这两个协议
// wishHappyBirthday(to:) 函数的参数 celebrator 的类型为 Named & Aged, 这意味着它不关心参数的具体类型, 只要参数符合这两个协议即可
// 注意: 协议合成不会生成新的, 永久的协议, 而是将多个协议中要求合成到一个只在局部作用域中有效的临时协议中
// 检查协议的一致性
// 你可以使用 类型转化 中 描述的 is 和 as 操作符来检查协议的一致性, 即是否符合某协议,并且可以指定转换到指定的协议类型, 检查很转化到某个协议类型在语法上和类型的检查和转换完全相同:
// is 用来检查视力是否符合某个协议, 若是返回 true, 否则返回 false
// as? 返回一个可选值, 当实例符合某个协议时, 返回类型为协议类型的可选值, 否则返回 nil
// as! 将实例强制向下转换到某个协议类型, 如果强制转换失败, 会引发运行时错误
protocol HasArea{
var area: Double { get }
}
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
}
}
// Circle 类把 area 属性实现为基于存储型属性 radius 的计算型属性, Country 类则把 area 属性实现为存储型属性, 这两个类都正确地符合了 HasArea 协议
// 下面是一个未遵循 HasArea 协议的类
class Animal{
var legs: Int
init(legs: Int) {
self.legs = legs
}
}
// Circle. Country ,Animal 并没有一个共同的基类, 尽管如此, 他们都是类, 他们的实例都可以作为 AnyObject 类型的值, 存储在同一个数组中:
let objects: [AnyObject] = [
Circle.init(radius: 2.0),
Country.init(area: 243_610),
Animal.init(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")
}
}
// 当迭代出的元素符合 HasArea 协议时, 将 as? 操作符返回的可选值通过可选绑定, 绑定到 objectWithArea 常量上, objectWithAera 是 HasArea 协议类型的实例, 因此 area 属性可以被访问和打印
// objects 数组中的元素的类型并不会因为强转而丢失类型信息, 他们仍然是 Circle , Country , Animal 类型, 然而,当他们被赋值给 objectWithArea 常量时, 只被视为 HasArea 类型, 因此只有 area 属性能被访问
// 可选的协议要求
// 协议可以定义 可选要求. 遵循协议的类型可以选择是否实现这些要求, 在协议中使用 optional 关键字作为前缀 来定义可选要求, 可选要求用在你需要和 OC 打交道的代码中, 协议和可选要求都必须带上 @objc 属性, 标记 @objc 特性的协议只能被继承自 OC 类的类 或者 @objc 类遵循, 其他类以及结构体和 枚举不能遵循这种协议
// 使用可选要求时(例如, 可选的方法或者属性) , 他们的类型会自动变成可选的, 比如, 一个类型为 (Int) -> String 的方法会变成 ((Int) -> String)?, 需要注意的是整个函数类型是可选的, 而不是返回值是可选的
// 协议中的可选要求可通过可选链式调用来使用, 因为遵循协议的类型可能没有实现这些可选要求, 类似 someOptionalMethod?(someArgument) 这样,你可以在可选方法名称后加上 ? 来调用可选方法。
@objc protocol CounterDataSource{
@objc optional func incrementForCount(count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
// 注意: 严格来讲, CounterDataSource 协议中的方法和属性都是可选的, 因此遵循协议的类可以不实现这些要求, 尽管技术上允许这么做, 不过做好不要这么写
class Counter{
var count = 0
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.incrementForCount?(count: count) {
count += amount
}else if let amount = dataSource?.fixedIncrement{
count += amount
}
}
}
// 协议扩展
// 协议可以通过扩展来为遵循协议的类型提供属性, 方法以及下标的实现, 你可以基于协议本身来实现这些功能, 而无需在每个遵循协议的类型中都重复同样的实现, 也无需使用全局函数
// 例如.
//extension SomeProtocol {
// func randomBool() -> Bool {
// return random() > 0.5
// }
//}
// 通过协议扩展, 所有遵循协议的类型, 都能自动获得这个扩展所增加的方法实现, 无需任何额外的修改
// 提供默认实现
// 可以通过协议扩展来为协议要求的属性, 方法以及下标 提供默认的实现, 如果遵循协议的类型为这些要求提供了自己的实现, 那么这些自定义实现将会替代扩展中的默认实现被使用
// 注意: 通过协议扩展为协议要求提供的默认实现和可选的协议要求不同, 虽然在这两种情况下, 遵循协议的类都无需自己实现这些要求, 但是通过扩展提供的默认实现可以直接调用, 而无需使用可选链式调用
extension PrettyTextRepresentable{
var prettyTextualDescription: String {
return textualDescription
}
}
// 为扩展协议添加限制条件
// 在扩展协议的时候, 可以指定一些限制条件, 只有遵循协议的类型满足这些限制条件时. 才能获得协议扩展提供的默认实现, 协议限制条件写在协议名之后, 使用 whrer 子句来描述,
// 例如,你可以扩展 CollectionType 协议,但是只适用于集合中的元素遵循了 TextRepresentable 协议的情况:
// extension CollectionType where Generator.Element: TextRepresentbble{
// // 扩展协议定义的内容
// }