Protocol 介绍
协议定义了适合一个特殊任务或功能的 方法,属性,和其他要求的蓝图。协议可以被 class, structure, enumeration
采纳(实现),来提供这些requirements的实现。即遵守协议的类型 要实现 协议中 定义的方法。满足了协议要求的任何类型 被称作 遵守了 该协议。
遵守协议的类型 除了 实现 指定的要求(requirements)外,你还可以扩展一个协议 来实现 其中的某些 requirements ,或者实现 遵守协议的类型可以利用的 额外的功能。
因为oc中的协议 中定义的方法 是可以声明为require
的,即必须要实现的方法,但是swift中没有这样的声明,所有遵守 协议的类型,协议中所定义的方法都要实现。但是如果某些方法认为不是必要实现的,可以通过扩展协议的方式,来实现某些 不必须实现的方法(即oc 中标注的 optional
方法),也就是对某些方法提供默认实现,那么遵守协议的类型 就不用再次实现这些方法。
这在扩展中有讲解到:https://www.jianshu.com/writer#/notebooks/48753120/notes/92016058
协议语法
protocol
关键字 + 协议名字
protocol SomeProtocol {
// protocol definition goes here
}
自定义类型遵守某个协议,协议的名字写在类型名后面用冒号分割,协议可以多继承,各个协议之间用逗号分割
struct SomeStructure: FirstProtocol, AnotherProtocol {
// structure definition goes here
}
如果一个类有父类,那么先写父类(写在类名之后,以冒号分割)再写协议名字,以逗号分割
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
// class definition goes here
}
- 属性要求
Protocol 可以要求任何遵守该协议的类型 提供 一个实例属性 或类型属性 通过一个特殊的名字和类型。protocol 没有指定这个属性是存储属性还是计算属性,它只指定所需的这个属性的名字和类型。还指定这个属性必须是只读的还是可读可写的。
如果协议要求属性是可读可写的,那么常量存储属性或只读计算属性无法满足该协议的要求。如果协议要求一个属性是可读的,那么任何一个属性都能满足该要求,属性被设置成 也 可写的 这也是有效的。
协议要求属性总是声明为可变的,使用关键字var
声明。可读可写的属性 要在类型声明后面加上{ get set }
。可读属性的声明要加{ get }
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
当你在协议中定义类型属性时,总是要求以static
关键字作为前缀,在类中实现类型属性时,可以以class
或者 static
作为前缀(但是注意:在class 中 声明类型存储属性时只能用 static
,类型计算属性可以用 class
)
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
class MyClass: AnotherProtocol {
//类型 存储 属性 在class 中 只能用 static
static var someTypeProperty: Int = 0
//类型计算属性 可以用 class
class var name: String {
return ""
}
}
下面看一个协议,只有一个实例属性
protocol FullyNamed {
var fullName: String { get }
}
此协议要求符合类型提供一个全名,此协议指定了 符合类型必须提供一个可读的实例属性,名称为fullName
,类型是string
类型
下面是一个结构体 实现了该协议:
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
以上结构体Person
的每一个实例都有唯一一个 类型为String的 存储属性,这符合了FullyNamed
协议的要求,说明Person
正确的实现了FullyNamed
协议,如果不满足协议要求,会有编译时错误
以下 是一个类实现了该协议,实现协议中的属性fullName
,是作为一个只读的计算属性实现的。
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")
方法要求
协议中可以指定 实例方法 和类型方法,让符合的类型去实现。这些方法作为协议声明的一部分与 正常的 实例 和类型 方法 的写法一样。但是没有花括号和方法体,也就是没有方法的实现部分,只有声明。允许有可变参数,遵循和正常的方法一样的规则,但是不能为协议定义中的方法参数指定默认值。
和类型属性的要求一样,在类型方法前 添加关键字static
,当被class
实现的时候,也可以使用 static
或 class
关键字
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
//类型方法 使用 static 关键字
static func someTypeMethod()
}
class MyClass: AnotherProtocol {
//类型 存储 属性 在class 中 只能用 static
static var someTypeProperty: Int = 0
//类型计算属性 可以用 class
class var name: String {
return ""
}
//可以使用 class 关键字
class func someTypeMethod() {
}
}
下面的例子是在协议中声明了一个实例方法, RandomNumberGenerator协议要求任何符合的类型必须提供一个实例方法,名字为random
,无参数,返回值类型为Double
.RandomNumberGenerator协议不对每个随机数的生成做任何假设,只是简单的要求生成器提供一个标准的方式来生成一个随机数。
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
}
}
let generator = LinearCongruentialGenerator()
-
Mutating
方法
对于一个方法来说,有时候更改它所属于的实例 是必要的。对于值类型(尤其是struct
和enum
),你应该在方法声明fun
前 添加关键字mutating
,来指明这个方法被允许 修改它所属实例的任何属性。如果你定义了一个协议实例方法,旨在要改变 采用该协议的任何类型的 实例。应该在协议前添加mutating
关键字作为协议定义的一部分。这使得结构体和枚举能够采用该协议,并满足它的要求。
下面例子定义了一个协议名称为Togglable,该协议定义了一个实例方法toggle
,根据方法名称可以猜测,该方法旨在切换或反转任何符合类型的状态,通常是通过修改该类型的属性。所以toggle()
方法使用mutating
关键词修饰,指明了该方法被调用的时候会改变符合实例的属性。
protocol Togglable {
mutating func toggle()
}
如果这个协议被结构体 和枚举 实现的话,那么结构体 或者枚举就要实现这个toggle()
方法,并且也得用mutating
关键词修饰。
下面的例子就是 名为OnOffSwitch
的枚举实现了以上协议。要在枚举的两个状态之间切换。并且由mutating
关键词修饰。
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()
初始化 要求
协议可能需要指定的初始化程序被符合的类型实现。协议中的初始化程序的定义和正常类型中的初始化定义的方式一样,但是没有大括号和方法体
protocol SomeProtocol {
init(someParameter: Int)
}
Class
类型 实现协议中的初始化程序
你可以在一个符合的类中去实现协议的初始化要求,作为指定构造器或者便利构造器。你必须在 该初始化方法之前 使用required
关键词
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// initializer implementation goes here
}
}
required
关键词的使用 确保你在符合的类 类型的所有子类上提供了一个初始化程序的 显示 或继承的实现,以便他们也能符合协议。
如果一个子类从父类那里继承了一个特定的初始化方法,并且也实现了 来自协议的 一个匹配的 初始化方法,那么在这个方法前 使用两个修饰符required
和override
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
}
}
作为类型的协议
协议本身不实现任何功能,尽管如此你还可以使用协议作为一个成熟的类型,使用协议作为类型通常被称作 “存在类型”,也就是 存在一个类型“T”,这个类型“T”实现了该协议
使用协议作为类型的场景:
1、作为方法的参数或者返回类型
2、作为常量,变量或者属性的类型
3、作为数组,字典或者其他容器中 元素的类型
例如:
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
}
}
委托
委托是一种设计模式,他能使类或结构体的某些职责移交(或委托)给另一种类型的实例。这种设计模式的实现是通过定义一个协议 封装要委托的职责,从而保证实现类型(也成为委托)能提供已经委托的功能。委托可以用来响应特定的操作,或者从外部资源检索数据而不需要知道该源的底层类型。
protocol DiceGame {
var dice: Dice { get }
func play()
}
protocol DiceGameDelegate: AnyObject {
func gameDidStart(_ game: DiceGame)
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
func gameDidEnd(_ game: DiceGame)
}
DiceGame
可以被任何类型采纳,DiceGameDelegate
只能被Class
类型采纳,为了避免强引用循环,delegate 都会被声明 weak
弱引用,一个只适用于类的协议,要使用关键字AnyObject
class SnakesAndLadders: DiceGame {
let finalSquare = 25
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
var square = 0
var board: [Int]
init() {
board = Array(repeating: 0, count: finalSquare + 1)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
}
weak 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
类采用了 DiceGame
协议,按协议要求 提供了一个 可读的 dice
属性和一个 paly()
方法(dice
属性被声明成了常量,因为初始化后不需要改变,协议只要求必须是可读的),可以看到delegate
属性被声明成了 可选的 DiceGameDelegate类型,因为玩游戏 delegate 并不是必须的。因为它是可选类型,所以会被自动的设置初始值为nil。此后游戏实例化器可以选择的将属性设置为合适的委托。因为协议DiceGameDelegate
是仅适用于类的,为了防止循环引用,设置delegate 为 weak
弱引用。
DiceGameDelegate
协议 声明了三个方法来追踪游戏的进度,这三个方法已经被整合进了play()
方法中的游戏逻辑中,当一个游戏开始或结束的时候被调用。因为delegate
属性是可选的DiceGameDelegate
类型,所以当play()
方法每次调用delegate
上的方法时都使用可选链(即?),如果 delegate
为nil,则会正常的调用失败而不会报错,
下面这个例子,声明了一个 DiceGameTracker
类,采用了DiceGameDelegate
协议
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 using a \(game.dice.sides)-sided dice")
}
func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {
numberOfTurns += 1
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()
使用扩展添加协议一致性
您可以扩展一个存在类型去采用和符合新的协议,即便您无权访问现有类型的源代码,扩展可以为现有类型添加新的属性,方法 和下标。因此可以添加协议可能要求的任何要求。
例如 下面这个协议TextRepresentable
可以被任何能够表示为文本的类型实现,这可能是类型本身的描述,也可以是当前状态的文本版本。
protocol TextRepresentable {
var textualDescription: String { get }
}
extension Dice: TextRepresentable {
var textualDescription: String {
return "A \(sides)-sided dice"
}
}
有条件的遵守协议
一个类型可能只在特定的条件下满足协议的要求,比如当类型的泛型参数符合协议时。你可以通过列出约束条件使这个类型有条件的符合协议,当扩展这个类型时。在你符合的协议名称后面 写 这些约束条件 通过 where
关键词。
下面这个例子就是让Array 符合了协议TextRepresentable
,只要它存储的元素类型符合TextRepresentable
协议
class Dice: TextRepresentable {
var textualDescription: String = "enen"
}
///有条件的遵守协议
extension Array: TextRepresentable where Element: TextRepresentable {
var textualDescription: String {
let itemsAsText = self.map {
$0.textualDescription
}
return "[" + itemsAsText.joined(separator: ",") + "]"
}
}
//使用
let a1 = Dice()
let myDice: Array = [ a1 ]
var text = myDice.textualDescription
使用扩展的方式 采纳协议
如果一个类型符合了协议的所有要求,但是还没有声明采用该协议,你可以让它采用这个协议,通过空的扩展
struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named \(name)"
}
}
extension Hamster: TextRepresentable {}
//使用
let simoTheHamster = Hamster(name: "Simon")
let somethingTextRespresentable: TextRepresentable = simoTheHamster
采用使用综合实现的协议
在一些简单的情况下,swift可以自动提供Equatable
,Hashable
,Comparable
方法实现,这时候你不用再写一些重复的样板代码来实现这些协议。
swift 为以下自定义类型提供了Equatable
协议的综合实现。
- 结构体,只有存储属性,并且属性 符合
Equatable
协议(也就是存储属性 的类型必须实现了Equatable 协议) - Enumerations(枚举),只有关联类型,并且关联类型符合
Equatable
协议(同上) - 没有关联类型的Enumerations(枚举)
为了能使用操作符==
, 在你定义类型的时候声明符合Equatable
协议,不用再亲自实现==
操作符,Equatable
协议提供了!=
方法的默认实现。
以下的例子定了了Vector3D
结构体,是一个三维位置向量(x, y, z),因为x ,y, z全部是Equatable
类型,Vector3D
接受等价(==)云算符的综合实现。
struct Vector3D: Equatable {
var x = 0.0, y = 0.0, z = 0.0
}
let twoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
let anotherTwoThreeFour = Vector3D(x: 2.0, y: 3.0, z: 4.0)
if twoThreeFour == anotherTwoThreeFour {
print("These two vectors are also equivalent.")
}
swift 提供Hashable
协议的综合实现,在以下的几种自定义类型的情况中:
- Structures(结构体),只有存储属性并且存储属性符合了
Hashable
协议 - Enumerations(枚举),只有关联类型,并且关联类型符合了
Hashable
协议 - Enumerations(枚举),没有任何关联类型
为了能接收hash(into:)
的综合实现,也就是为了能调用hash(into:)
这个方法,在定义类型的时候(在声明这个自定义类型的文件中)要声明符合Hashable
协议 (与)Equatable
使用方式一样
swift为没有值类型的枚举 提供Comparable
协议的综合实现。如果枚举有关联类型,这个关联类型必须符合Comparable
协议。为了能接收<
操作符,在你声明自定义枚举类型的时候 声明 符合 Comparable
协议。此时就不用再自己去实现<
操作符的实现,Comparable
协议默认实现<=, >, >=
这些其他操作符实现。
enum SkillLevel: Comparable {
case beginner
case intermediate
//关联类型Int 符合 Comparable协议
case expert(starts: Int)
}
var levels = [SkillLevel.intermediate, SkillLevel.beginner,
SkillLevel.expert(stars: 5), SkillLevel.expert(stars: 3)]
for level in levels.sorted() { //sorted 方法内部调用了Comparable 中的方法
print(level)
}
协议类型的集合
数组或者字典里可以存储 协议类型的数据元素,例如
let things: [TextRepresentable] = [game, d12, simonTheHamster]
协议继承
一个协议可以继承一个或者多个其他的协议,可以在它继承的需求之上添加更多的需求,协议继承的语法和class 继承的语法类似,多个继承的协议之间以,
分割
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// protocol definition goes here
}
下面的例子,PrettyTextRepresentable
协议 继承 TextRepresentable
协议
protocol TextRepresentable {
var textualDescription: String { get }
}
protocol PrettyTextRepresentable: TextRepresentable {
var prettyTextualDescription: String { get }
}
PrettyTextRepresentable 协议 继承自 TextRepresentable 协议,所有采用PrettyTextRepresentable 协议的类型,都必须满足 TextRepresentable 协议强制执行的所有要求,加上 PrettyTextRepresentable协议强制执行的额外要求,这个例子中 PrettyTextRepresentable 协议添加了一个 可读的属性prettyTextualDescription
,返回 String 类型
SnakesAndLadders
类可以使用扩展 符合 PrettyTextRepresentable 协议
extension SnakesAndLadders: TextRepresentable {
var textualDescription: String {
return "A game of Snakes and Ladders with \(finalSquare) squares"
}
}
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
}
}
仅适用于Class 类型的协议
你可以限制一个协议的采用者为Class类型,而不是 结构体也不是枚举,方式是:在协议继承列表中 添加 AnyObject
协议
例如:
protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {
// class-only protocol definition goes here
}
协议组合
有时候一个类型会同时实现多个协议,你可以使用协议组合 将多个协议 组合成 一个需求,协议组合就好像是 你定义了一个临时的本地协议,该协议 具有组合中 所有的协议,协议组合 不会定义任何新的协议要求
协议组合的形式 是 SomeProtocol
& AnotherProtocol
。你可以列举出你需要的尽可能多的协议。以&
分割。
协议组合 除了是 将多个协议组合在一起的方式外,还可以包含一个Class 类型,代表父类。
下面的例子,将两个 名为Named 和Aged 协议 组合成一个对函数参数的单一的协议组合要求
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(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
以上的例子 中定义了一个方法wishHappyBirthday(to:)
,该方法接收一个参数类型为Named & Aged
,意思就是 “任何同时符合了Named 和Aged 两个协议的类型”,至于哪种特定类型被传入了方法并不重要,只要它实现这两个被要求的协议。
下面 是一个将上例中 Named
协议和 Location
类组合在一起的例子:
class Location {
var latitude: Double
var longitude: Double
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
}
}
class City: Location, Named {
var name: String
init(name: String, latitude: Double, longitude: Double) {
self.name = name
super.init(latitude: latitude, longitude: longitude)
}
}
func beginConcert(in location: Location & Named) {
print("Hello, \(location.name)!")
}
//使用
let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
beginConcert(in:)
方法需要一个 Location & Named
类型作为参数,也就是 一个类型 是Location
的子类 同时它也实现了Named
协议,在这个例子中City
类满足了这两个要求
检查协议一致性
你可以使用is
和as
操作符来检查协议一致性,并且转换成一个特定的协议。检查和转换成一个协议的语法和 检查和转换成一个类型的 语法是一样的。
- 如果
is
操作符返回 true ,说明 这个对象 遵守了这个协议,如果为false 就是没有遵守 -
as?
会返回一个 协议类型的可选值,如果该对象没有遵守这个协议 就会返回nil -
as!
会强制转换成协议类型,如果转换不成功 就会发生运行时错误
下面例子定义了一个名字为HasArea
的协议,有两个类Circle
和Country
,这两个类都遵守了HasArea
协议。
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
实现了HasArea
协议中的要求,area 作为计算属性实现的,而Country
类中 area 作为存储属性实现了协议的要求。
下面Animal
类 没有遵守 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 {
print("Area is \(objectWithArea.area)")
} else {
print("Something that doesn't have an area")
}
}
可选的协议要求
你可以为协议定义可选要求,这些要求并不是必须实现的 对于 遵守这个协议的类型来说,作为协议定义的一部分,可选要求以optional
修饰符为前缀,可选要求是可用的,为了方便您编写与OC混编的代码。此时,协议 和 可选方法 都要使用@objc
标识符标记。请注意:被@objc
标识的协议只能被从Objective-C 类 或其他@objc
类继承的类 类型 所采用。这种协议不能被 结构体 和 枚举所采用。
当你调用可选的方法或属性时,它的类型会自动变成可选类型。例如一个方法类型(Int) -> String
会变成((Int) -> String)?
,请注意,整个函数的类型都包含在可选项中,而不是方法的返回值中。
可以 使用可选链调用可选协议要求(协议中的可选方法或者属性),考虑到需求(方法或属性)没有被遵守协议的类型所实现的可能性。在调用的时候,在可选的要求(方法或属性)名称后面添加?
,来检查可选方法是否实现。
下面的例子定义了一个整数计数类Counter
,它使用外部数据源来提供它的增量。这个数据源是由 CounterDataSource
协议定义的,该协议有两个可选的要求。
@objc protocol CounterDataSource {
@objc optional func increment(forCount count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
该协议定义了一个可选的方法要求和一个可选的属性要求,这些要求为数据源提供了两种不同的方式为Counter
实例提供适当的增量。
Counter
有一个可选的CounterDataSource
类型的 dataSource属性
class Counter {
var count = 0
var dataSource: CounterDataSource?
func increment() {
//调用 可选方法increment,后面要加 ?,检查它是否被实现
if let amount = dataSource?.increment?(forCount: count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
count += amount
}
}
}
请注意这里有两个级别的可选链。首先,dataSource
可能为nil,所以在它后面标记了一个?
号,表明当DataSource 不为nil 的时候 会调用increment(forCount:)
方法。其次,尽管dataSource
存在,也不能保证它实现了increment(forCount:)
方法,因为这是个可选方法,不一定非要去实现。那么 increment(forCount:)
方法 没有被实现的可能性也会被可选链处理,只有当increment(forCount:)
方法被实现的时候 才会被调用,这就是为什么increment(forCount:)
方法也会被?
标记的原因。
因为以上两种情况,increment(forCount:)
方法调用可能会失败,这个方法会返回一个可选的Int类型。这是对的,尽管这个方法在协议的定义中 返回的是 非可选 Int类型,尽管有两个可选链,一个接一个,结果仍然会被包装成一个可选值。
如上代码所示,当调用increment(forCount:)
方法的时候,它返回的可选的Int类型 会被解包 赋值给常量amount
,如果返回的这个可选的Int 类型确实包含值,也就是dataSource
和方法都存在,方法的返回值 也就是 解包后的amount
会被添加到存储属性count
里面,完成增量。
//使用,其实ThreeSource 也可以不继承自NSObject
//继承自NSObject,是可以当成OC对象,可以使用运行时,可以和OC混编
class ThreeSource: NSObject, CounterDataSource {
let fixedIncrement = 3
}
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
counter.increment()
print(counter.count)
}
协议扩展
可以扩展协议来为要遵守此协议的类型 提供方法,初始化器,下标,和计算属性的实现。这允许你在协议中定义行为,而不是在每一个类型中或全局函数中 再单独定义。
例如:RandomNumberGenerator
协议可以被扩展来提供一个randomBool()
方法
extension RandomNumberGenerator {
func randomBool() -> Bool {
return random() > 0.5
}
}
提供默认的实现
你可以在协议扩展中为 该协议中任何方法,或者计算属性要求提供默认的实现。如果符合类型(要实现该协议的类型)提供了自己所需方法或属性的实现,则将使用该实现 而不是扩展中提供的实现。也就是 在符合类型中 提供的实现将覆盖 扩展中 默认的实现。
protocol TextRepresentable {
var textualDescription: String { get }
}
protocol PrettyTextRepresentable: TextRepresentable {
var prettyTextualDescription: String { get }
}
extension PrettyTextRepresentable {
var prettyTextualDescription: String {
return textualDescription
}
}
向协议扩展添加约束
当你定义一个协议扩展的时候,你可以指定符合类型必须满足的约束条件在 扩展的方法和属性可用之前。将这些约束条件写在 你扩展的协议名称之后,通过where
通用语法。
例如你可以为协议Collection
定义一个扩展,此扩展适用于任何 元素符合Equatable
协议的集合,通过给集合的元素添加Equatable
协议,你可以使用==
或者!=
操作符 来比较两个元素
extension Collection where Element: Equatable {
func allEqual() -> Bool {
for element in self {
if element != self.first {
return false
}
}
return true
}
}
//Array类型 本身遵守了Collection 协议,因为arr 中的元素 为Int,遵守了Equatable 协议
//所以 此时的 arr 可以调用 allEqual 方法
let arr: [Int] = [1,2,3]
let _ = arr.allEqual()
其他
- 在实现协议的时候,有两种方式组织你的代码
1> 使用//MARK: 注释来分割协议实现和其他的代码
2>使用extension 在类/结构体已有代码外,但在同一个文件内