1,协议:
(属性要求)协议可以要求所有遵循该协议的类型提供特定名字和类型的实例属性或类型属性。协议并不会具体说明属性是储存型属性还是计算型属性——它只具体要求属性有特定的名称和类型。协议同时要求一个属性必须明确是可读的或可读的和可写的。
属性要求定义为变量属性,在名称前面使用 var 关键字。可读写的属性使用 { get set } 来写在声明后面来明确,使用 { get } 来明确可读的属性。
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
2,在协议中定义类型属性时,在前面添加static 关键字。当类的实现使用 class 或 static 关键字前缀声明类型属性要求时:
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
3,协议—异变方法要求:
有时一个方法需要改变(或异变)其所属的实例。例如值类型的实例方法(即结构体或枚举),在方法的 func 关键字之前使用 mutating 关键字,来表示在该方法可以改变其所属的实例,以及该实例的所有属性。
4,如果你在协议中标记实例方法需求为 mutating ,在[表情]为类实现该方法的时候不需要写 mutating 关键字。 mutating 关键字只在结构体和枚举类型中需要书写。
5,初始化器要求:
协议可以要求遵循协议的类型实现指定的初始化器。和一般的初始化器一样,只用将初始化器写在协议的定义当中,只是不用写大括号也就是初始化器的实体:
protocol SomeProtocol {
init(someParameter: Int)
}
协议初始化器要求的类实现;
你可以通过实现指定初始化器或便捷初始化器来使遵循该协议的类满足协议的初始化器要求。在这两种情况下,你都必须使用 required 关键字修饰初始化器的实现:
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
}
}
6,在遵循协议的类的所有子类中, required 修饰的使用保证了你为协议初始化器要求提供了一个明确的继承实现。由于 final 的类不会有子类,如果协议初始化器实现的类使用了 final 标记,你就不需要使用 required 来修饰了。因为这样的类不能被继承子类。如果一个子类重写了父类指定的初始化器,并且遵循协议实现了初始化器要求,那么就要为这个初始化器的实现添加 required 和 override 两个修饰符;
7,将协议作为类型:
任意协议都可以变为一个功能完备的类型在代码中使用。由于它是一个类型,你可以在很多其他类型可以使用的地方使用协议,包括:
在函数、方法或者初始化器里作为形式参数类型或者返回类型;
作为常量、变量或者属性的类型;
作为数组、字典或者其他存储器的元素的类型。
由于协议是类型,要开头大写来匹配 Swift 里其他类型名称格式(比如说 Int 、 String 还有 Double )。
8,protocol RandomNumberGenerator {
func random() -> Double
}
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
}
}
9,Dice 也有一个初始化器,来设置初始状态。这个初始化器有一个形式参数叫做 generator ,它同样也是 RandomNumberGenerator 类型。你可以在初始化新的 Dice 实例的时候传入,【一个任意遵循这个协议的类型的值】到这个形式参数里。
10,协议类型属性:
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).truncatingRemainder(dividingBy:m))
return lastRandom / m
}
}
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
}
11,委托是一个允许类或者结构体放手(或者说委托)它们自身的某些责任给另外类型实例的设计模式。这个设计模式通过定义一个封装了委托责任的协议来实现,比如遵循了协议的类型(所谓的委托)来保证提供被委托的功能。委托可以用来响应一个特定的行为,或者从外部资源取回数据而不需要了解资源具体的类型。
var delegate: DiceGameDelegate?
协议类型的集合:
协议可以用作储存在集合比如数组或者字典中的类型,如同在协议作为类型。
12,协议继承:
协议可以继承一个或者多个其他协议并且可以在它继承的基础之上添加更多要求。协议继承的语法与类继承的语法相似,只不过可以选择列出多个继承的协议,使用逗号分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// protocol definition goes here
}
13,类专用的协议:
通过添加 AnyObject 关键字到协议的继承列表,你就可以限制协议只能被【类类型】采纳(并且不是结构体或者枚举)。
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
// class-only protocol definition goes here
}
SomeClassOnlyProtocol 只能被类类型采纳。如果在结构体或者枚举中尝试采纳 SomeClassOnlyProtocol 就会出发编译时错误。
14,协议组合:
要求一个类型一次遵循多个协议是很有用的。你可以使用协议组合来复合多个协议到一个要求里。协议组合行为就和你定义的临时局部协议一样拥有构成中所有协议的需求。协议组合不定义任何新的协议类型。
协议组合使用 SomeProtocol & AnotherProtocol 的形式。
你可以列举任意数量的协议,用和符号连接( & ),使用逗号分隔。除了协议列表,【协议组合也能包含类类型】,这允许你标明一个需要的父类。
15,协议遵循的检查:
你可以使用类型转换中描述的 is 和 as 运算符来检查协议遵循,还能转换为特定的协议。检查和转换协议的语法与检查和转换类型是完全一样的;
as? 版本的向下转换运算符返回协议的可选项,如果实例不遵循这个协议的话值就是 nil ;
as! 版本的向下转换运算符强制转换协议类型并且在失败是触发运行时错误。
16,可选协议要求:
协议和可选要求必须使用 @objc 标志标记。注意 @objc 协议只能被继承自 Objective-C 类或其他 @objc 类采纳。它们不能被结构体或者枚举采纳。
@objc protocol CounterDataSource {
@objc optional func increment(forCount count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
17,协议扩展:
协议可以通过扩展来提供方法和属性的实现以遵循类型。这就允许你在协议自身定义行为,而不是在每一个遵循或者在全局函数里定义。
18,协议扩展(提供默认实现):
你可以使用协议扩展来给协议的任意方法或者计算属性要求提供默认实现。如果遵循类型给这个协议的要求提供了它自己的实现,那么它就会替代扩展中提供的默认实现。
通过扩展给协议要求提供默认实现与可选协议要求的区别是明确的。尽管遵循协议都不需要提供它们自己的实现。有默认实现的要求不需要使用可选链就能调用。
protocol TextRepresentable {
var textualDescription: String { get }
}
protocol PrettyTextRepresentable: TextRepresentable {
var prettyTextualDescription: String { get }
}
extension PrettyTextRepresentable {
var prettyTextualDescription: String {
return textualDescription
}
}
19,给协议扩展添加限制:
当你定义一个协议扩展,你可以明确遵循类型必须在扩展的方法和属性可用之前满足的限制。在扩展协议名字后边使用 where 分句来写这些限制。比如说,你可以给 Collection 定义一个扩展来应用于任意元素遵循上面 TextRepresentable 协议的集合。
extension Collection where Iterator.Element: TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map { $0.textualDescription }
return "[" + itemsAsText.joined(separator: ", ") + "]"
}
}
17,泛型版本的栈:struct Stack {
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
}
var stackOfStrings = Stack()
stackOfStrings.push("uno")
18,扩展一个泛型类型:
当你扩展一个泛型类型时,不需要在扩展的定义中提供类型形式参数列表。
类型约束语法:在一个类型形式参数名称后面放置一个类或者协议作为形式参数列表的一部分,并用冒号隔开,以写出一个类型约束。下面展示了一个泛型函数类型约束的基本语法(和泛型类型的语法相同):
func someFunction
// function body goes here
}
类型约束的应用:func findIndex
for (index, value) in array.enumerated() {
if value == valueToFind {
return index
}
}
return nil
}
19,Container 协议定义了三个所有容器必须提供的功能:
必须能够通过 append(_:) 方法向容器中添加新元素;
必须能够通过一个返回 Int 值的 count 属性获取容器中的元素数量;
必须能够通过 Int 索引值的下标取出容器中每个元素。
任何遵循 Container 协议的类型必须能指定其存储值的类型。尤其是它必须保证只有正确类型的元素才能添加到容器中,而且该类型下标返回的元素类型必须是正确的。
20,关联类型:
定义一个协议时,有时在协议定义里声明一个或多个关联类型是很有用的。关联类型给协议中用到的类型一个占位符名称。直到采纳协议时,才指定用于该关联类型的实际类型。关联类型通过 associatedtype 关键字指定。
protocol Container {
associatedtype ItemType
mutating func append(_ item: ItemType)
var count: Int { get }
subscript(i: Int) -> ItemType { get }
}
21,struct IntStack: Container {
var items = [Int]()
mutating func push(_ item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// conformance to the Container protocol
typealias ItemType = Int
mutating func append(_ item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}
22,
struct Stack
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// conformance to the Container protocol
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
这次,类型形式参数 Element 用于 append(_:) 方法的 item 形式参数和下标的返回类型。因此,对于这个容器,Swift可以推断出 Element 是适用于 ItemType 的类型。
23,扩展现有类型来指定关联类型:
Swift 的 Array 类型已经提供了 append(_:) 方法、 count 属性、用 Int 索引取出其元素的下标。这三个功能满足了 Container 协议的要求。这意味着你可以通过简单地声明 Array 采纳协议,扩展 Array 使其遵循 Container 协议。通过一个空的扩展实现,如使用扩展声明采纳协议:
extension Array: Container {}
数组已有的 append(_:) 方法和下标使得Swift能为 ItemType 推断出合适的类型,就像上面的泛型 Stack 类型一样。定义这个扩展之后,你可以把任何 Array 当做一个 Container 使用。
24,泛型Where分句:
类型约束允许你在泛型函数或泛型类型相关的类型形式参数上定义要求。
类型约束在为关联类型定义要求时也很有用。通过定义一个泛型Where分句来实现。泛型 Where 分句让你能够要求一个关联类型必须遵循指定的协议,或者指定的类型形式参数和关联类型必须相同。
泛型 Where 分句以 Where 关键字开头,后接关联类型的约束或类型和关联类型一致的关系。
泛型 Where 分句写在一个类型或函数体的左半个大括号前面。
func allItemsMatch
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
if someContainer.count != anotherContainer.count {
return false
}
for i in 0.. if someContainer[i] != anotherContainer[i] { return false } } return true } C1 必须遵循 Container 协议(写作 C1: Container ); C2 也必须遵循 Container 协议(写作 C2: Container ); C1 的 ItemType 必须和 C2 的 ItemType 相同(写作 C1.ItemType == C2.ItemType ); C1 的 ItemType 必须遵循 Equatable 协议(写作 C1.ItemType: Equatable )。 25,带有泛型 Where 分句的扩展: extension Stack where Element: Equatable { func isTop(_ item: Element) -> Bool { guard let topItem = items.last else { return false } return topItem == item } } 26,可以写一个泛型 where 分句来要求 Item 为特定类型。比如: extension Container where Item == Double { func average() -> Double { var sum = 0.0 for index in 0.. sum += self[index] } return sum / Double(count) } } 27,关联类型的泛型 Where 分句: protocol Container { associatedtype Item mutating func append(_ item: Item) var count: Int { get } subscript(i: Int) -> Item { get } associatedtype Iterator: IteratorProtocol where Iterator.Element == Item func makeIterator() -> Iterator } 28,对于一个继承自其他协议的协议来说,你可以通过在协议的声明中包含泛型 where 分句来给继承的协议中关联类型添加限定。比如说,下面的代码声明了一个 ComparableContainer 协议,它要求 Item 遵循 Comparable : protocol ComparableContainer: Container where Item: Comparable { } 29,泛型下标:下标可以是泛型,它们可以包含泛型 where 分句。你可以在 subscript 后用尖括号来写类型占位符,你还可以在下标代码块花括号前写泛型 where 分句。举例来说:extension Container { subscript(indices: Indices) -> [Item] where Indices.Iterator.Element == Int { var result = [Item]() for index in indices { result.append(self[index]) } return result } } 这个 Container 协议的扩展添加了一个接收一系列索引并返回包含给定索引元素的数组。这个泛型下标有如下限定: 在尖括号中的泛型形式参数 Indices 必须是遵循标准库中 Sequence 协议的某类型; 下标接收单个形式参数, indices ,它是一个 Indices 类型的实例; 泛型 where 分句要求序列的遍历器必须遍历 Int 类型的元素。这就保证了序列中的索引都是作为容器索引的相同类型。 合在一起,这些限定意味着传入的 indices 形式参数是一个整数的序列。 30,内存安全性: >>>Swift 通过要求标记内存位置来确保代码对内存有独占访问权,以确保了同一内存多访问时不会冲突。 输入输出形式参数的访问冲突: var stepSize = 1 func increment(_ number: inout Int) { number += stepSize } increment(&stepSize) stepSize 的读取访问与 number 的写入访问重叠了。 number 和 stepSize 引用的是同一内存地址。读取和写入访问引用同一内存并且重叠,产生了冲突。 >>>输入输出形式参数的长时写入访问的另一个后果是传入一个单独的变量作为实际形式参数给同一个函数的多个输入输出形式参数产生冲突。比如: func balance(_ x: inout Int, _ y: inout Int) { let sum = x + y x = sum / 2 y = sum - x } var playerOneScore = 42 var playerTwoScore = 30 balance(&playerOneScore, &playerTwoScore) // OK [表情]balance(&playerOneScore, &playerOneScore) // Error: Conflicting accesses to playerOneScore 上边 balance(_:_:) 修改它的两个形式参数将它们的总数进行平均分配。用 playerOneScore 和 playerTwoScore 作为[表情]实际参数不会产生冲突——一共有两个写入访问在同一时间重叠,但它们访问的是不同的内存地址。相反,传入 playerOneScore 作为两个[表情]形式参数的值则产生冲突,因为它尝试执行两个写入访问到同一个内存地址且是在同一时间执行。 extension Player { mutating func shareHealth(with teammate: inout Player) { balance(&teammate.health, &health) } } var oscar = Player(name: "Oscar", health: 10, energy: 10) var maria = Player(name: "Maria", health: 5, energy: 10) oscar.shareHealth(with: &maria) // OK oscar.shareHealth(with: &oscar) ❌ // Error: conflicting accesses to oscar 31,在方法中对 self 的访问冲突: >>>结构体中的异变方法可以在方法调用时对 self 进行写入访问。 >>>属性的访问冲突: 像是结构体、元组以及枚举这些类型都是由独立值构成,比如结构体的属性或者元组的元素。由于这些都是值类型,改变任何一个值都会改变整个类型,意味着读或者写访问到这些属性就需要对整个值进行读写访问。比如说,对元组的元素进行重叠写入访问就会产生冲突: var playerInformation = (health: 10, energy: 20) balance(&playerInformation.health, &playerInformation.energy) // ❌Error: conflicting access to properties of playerInformation 32,具体来说,如果下面的条件可以满足就说明重叠访问结构体的属性是安全的: 你只访问实例的存储属性,不是计算属性或者类属性; 结构体是局部变量而非全局变量; 结构体要么没有被闭包捕获要么只被非逃逸闭包捕获。