第二十一章 协议。
14. Optional Protocol Requirements (可选协议要求)
我们可以为协议定义一个可选的要求(optional requirement),这些要求可以勿需被所遵循的协议类型所实现,我们可以在协议的定义中以添加optional
修饰符作为前缀的方法作为这个协议的可选要求。当我们写的代码要与objective-c作交互的时候,要加上@objc
属性,需要注意的是@objc
协议只能被继承于Objective-c的类所采用或@objc
的类。而@objc
协议不能内结构体或枚举所采用。
当我们在可选要求中使用方法或属性的时候,它的类型会自动变成可选,举个例子,类型的方法由(Int) -> String
变成((Int) -> String)?
,需要注意的是整个函数类型将会包装在可选类型里面,而不是返回类型变成可可选的。在可选链里面可以调用这个可选协议要求,因为遵循协议的类型可能没有实现这些可选要求。类似someOptionalMethod?(someArgument)
这样,你可以在可选方法名称后加上 ?
来调用可选方法,详情见可选链章节。
@objc protocol CounterDataSource {
// 可选方法要求
@objc optional func increment(forCount count: Int) -> Int
// 可选属性要求
@objc optional var fixedIncrement: Int { get }
}
上面的这个例子是定义了一个整数计数的类Counter
它使用外部的数据源来提供每次的增量。数据源由CounterDataSource
协议定义,包含两个可选要求
这个CounterDataSource协议定义了一个可选方法要求increment(forCount:)
和一个可选属性要求fixedIncrement
,这两个要求定义了两个不同的方法来为数据源提供一个Counter实例的合适的增量方法,
下例定义的是一个Counter类,它有两个类型为CounterDataSource?
的属性dataSource。
class Counter {
var count = 0
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.increment?(forCount: count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
}
}
}
Counter
这个类存储的是当前变量count
的值,并且这个类还定义了一个方法increment
,每一次调用这个方法都会增加变量count的值。源码的第五详解 counter类的这个方法increment()
第一次尝试从dataSource使用increment(forCount:)
方法来获得每次的增量。increment()方法将会使用可选链来调用这个increment(forCount:)方法,并把count当前的值作为传递给方法的一个单一的参数。
还需要注意的是源码的第五行这里有两层可选链的使用。第一个也就是这个dataSource?
调用后面的。也就是说这里的dataSource可能是nil,所以它后面有一个?
符号。并可这个dataSource?后面用dot语法进行调用则说明了这个dataSource不能是nil,第二个呢即便这个正式存在着,也不能保证会调用和实现increment(forCount:)。因为这increment(forCount:)还是一个可选的请求,在dataSource?.increment?(forCount: count)
里面只有increment?(forCount: count)真实存在,才能调用它也就是说用dataSource
去调用increment(forCount:)
,两层可选链在这里使用是想进一步说明既要dataSource存在,也要increment(forCount:)存在,那么最后则会用dataSource来调用increment(forCount:)。
调用increment(forCount:)
肯能会失败,无非就是两个原因,这个调用会返回一个可选的Int
值,也就是说increment(forCount:)在CounterDataSource
被定义为一个返回为非可选的Int
值。在这里有两层可选链的操作,详细的见链接多层可选链章节。在调用increment(forCount:)后返回的这个可选的Int
值被绑定在amount
的常量中。最后使用可选绑定,如果说这个可选的Int
值真实存在,那么代理和方法也都存在,最后强制读取amount的值并把它添加到存储属性count
里面。
如果说没用从increment(forCount:)
获取任何值,不是dataSource
是nil
,就是这个dataSource不实现increment(forCount:)方法,那么increment()方法则会尝试从dataSource的fixedIncrement属性里面获取值。这个fixedIncrement
同样也是一个可选的请求,所以它的同样是一个可选的值,即便是fixedIncrement在CounterDataSource
协议中被定义为一个非可选的Int
属性,它的值依然是一个可选的Int值。
下面的例子展示了CounterDataSource的简单实现。ThreeSource类遵循了CounterDataSource协议,它实现了可选属性fixedIncrement,每次会返回3 :
// type subclass superclass protocol
class ThreeSource: NSObject, CounterDataSource {
let fixedIncrement = 3
}
可以使用ThreeSource实例作为新的counter实例的数据源
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
counter.increment()
print(counter.count)
}
// 3
// 6
// 9
// 12
上面的代码中,创建了一个新的Counter
实例,设置它的数据源为一个新的ThreeSource
实例,并且调用这个counter
的increment()
方法四次,和预想的一样每一次调用increment()都会将count
的属性增加三。
下面是一个更加复杂的数据源TowardsZeroSource
,这个数据源是counter的实例从当前向上或向下计算使的最后的值为0
。
class TowardsZeroSource: NSObject, CounterDataSource {
func increment(forCount count: Int) -> Int {
// 条件语句用于当前降值至0
if count == 0 {
return 0
} else if count < 0 {
return 1
} else {
return -1
}
}
}
这个类TowardsZeroSource
从协议CounterDataSource
中实现的是这个increment(forCount:)
方法,从前例中可以看出这是一个可选的方法。使用count
的参数值进行计算,如果说count当前的值是0
那么则没有后面的计算了。
我们可以使用TowardsZeroSource
的实例和已有的Counter
的实例来从-4
计算到0
,一旦Counter计算到0 后面就没有任何计算会产生了。
counter.count = -4
counter.dataSource = TowardsZeroSource()
for _ in 1...5 {
counter.increment()
print(counter.count)
}
// -3
// -2
// -1
// 0
// 0
15. Protocol Extensions (协议扩展)
协议可以通过扩展来为方法,构造器,下标,和计算型属性提供实现,通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个遵循协议的类型中都重复同样的实现,也无需使用全局函数。
举个例子,协议RandomNumberGenerator
可以通过扩展来提供一个randomBool()
的方法,使用协议中的请求方法random()
来返回一个Bool
值。
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.3746499199817101
print("And here's a random Boolean: \(generator.randomBool())")
// 输出:And here's a random Boolean: true
协议扩展可以添加确定类型的一个实现,但是不能再次添加协议扩展,或继承自另一个协议,协议继承总是会定义在协议的声明中。
15.1 Providing Default Implementations (提供默认实现)
我们可以使用协议扩展来为协议提供一个默认的方法或计算型属性的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。
举个例子,PrettyTextRepresentable
是一个继承TextRepresentable
的协议,它可以提供一个默认的prettyTextualDescription
属性的实现,来返回一个读取在textualDescription
属性上的返回值。
extension PrettyTextRepresentable {
var prettyTextualDescription: String {
return textualDescription
}
}
15.2 Adding Constraints to Protocol Extensions (为协议扩展添加限制)
当我们定义一个协议扩展的时候,可以指定一些限制条件只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供 的默认实现。这些限制条件写在协议名之后,使用where子句来描述,更多详见下章泛型where子句,
举个例子,如果我们为协议Collection
定义了一个扩展,但是只适用于集合中的元素遵循了 Equatable协议的情况,通过为协议Equatable协议中的元素添加限制条件,我们可以使用==
和!=
,来检查两个元素之间的相等性和不相等性。这个allEqual()
方法是用来检查所有的元素的相等性,返回的是true
则说明集合里的所有元素都相同。
extension Collection where Element: Equatable {
func allEqual() -> Bool {
for element in self {
if element != self.first {
return false
}
}
return true
}
}
考虑到有两组不同的整数数组,一个数组里面的元素全部相同,另一组里面的元素不相同。因为这个数组遵循的是Collection
和整数遵循的是Equatable
,equalNumbers
和differentNumbers
都可以使用这个allEqual()
方法
let equalNumbers = [100, 100, 100, 100, 100]
let differentNumbers = [100, 100, 200, 100, 200]
print(equalNumbers.allEqual())
// Prints "true"
print(differentNumbers.allEqual()
// Prints "false"