Swift文档Chapter 21 协议
协议规定了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。
协议语法
protocol SomeProtocol {
// 这里是协议的定义部分
}
自定义类型如果需要遵循协议,需要用:
分隔,遵循多个协议使用,
分隔。
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 这里是结构体的定义部分
}
如果这个类型拥有一个父类,那么父类名放在协议名之前:
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 这里是类的定义部分
}
属性要求
协议可以要求提供特定名称的实例属性或者类型属性,不指定是计算属性还是存储属性。同时,也可以定义属性是可读的还是可读可写的。
如果协议只要求可读,实现中可以是可读可写的。协议用var
来声明变量属性,在声明类型后加上{set get}
表示属性可读可写,加上{get}
表明属性可读。
使用static
前缀可以声明类型属性,在类实例实现时可以使用class
关键字。
方法要求
协议中方法的定义和其他类型中完全相同使用func
关键字,只不过没有花括号的函数体。在协议中定义类方法的时候,总是使用static
关键字作为前缀。即使在类实现时,类方法要求使用class
或static
作为关键字前缀。
异变方法要求
有时需要在方法中改变(或异变)方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将mutating
关键字作为方法的前缀,写在func
关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。如果是类类型实现mutating
函数可以不加关键字,但是枚举类型和结构体类型需要。
构造器要求
协议可以要求遵循协议的类型实现指定的构造器。你可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
protocol SomeProtocol {
init(someParameter: Int)
}
协议构造器要求的类实现
类实现协议的构造器,无论是指定构造器,还是作为便利构造器,都需要加上required
修饰符。
required
修饰符可以确保所有子类也必须提供此构造器实现,如果是final
类,那么不需要required
修饰符。
如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注required
和override
修饰符。
可失败构造器要求
协议还可以为遵循协议的类型定义可失败构造器要求。遵循协议的类型可以通过可失败构造器(init?
)或非可失败构造器(init
)来满足协议中定义的可失败构造器要求。
协议作为类型
协议本身并未实现任何功能,但是协议可以作为一个类型来使用。协议作为类型使用通常被称为存在类型。
协议可以像普通类型一样使用:
- 作为函数,方法以及构造器里的参数类型或者返回值类型;
- 作为常量,变量或者属性的类型;
- 作为数组,字典等其他容器的元素类型。
协议是一种类型,因此命名也要使用大写字母开头的驼峰命名法。
当我们在类型中定义协议参数的时候,我们只需要传入一个遵循了该协议的类型即可。我们只能使用协议内定义的方法和类型,不可以使用底层的类型提供的方法和属性。
委托
委托是一种设计模式,允许类或者结构体把一部分功能委托给其他类型的实例。委托模式需要定义协议封装需要实现的功能,能确保遵循协议的类型能提供这些功能。
在扩展里添加协议遵循
在无法修改源代码的情况下,我们可以扩展令已有类型遵循并符合协议。通过扩展可以给类型增加新的协议并使其遵循这个协议。和原始协议中定义并遵循协议是一样的。需要使用extension
关键字。
有条件地遵循协议
如果我们在泛型中使用扩展添加协议,可能只符合某一种类型。因此我们使用where
扩展类型时列出限制让泛型类型有条件地遵循某协议。
在扩展里声明采纳协议
如果说某个类型已经实现了协议,但是并没有遵循该协议。那么使用空的扩展让它来遵循协议。
struct Hamster {
var name: String
var textualDescription: String {
return "A hamster named \(name)"
}
}
extension Hamster: TextRepresentable {}
即使类型已经满足了协议要求,也不会自动去遵循协议。必须显式地遵循协议。
协议类型的集合
协议类型可以在数组或者字典这样的集合中使用。
协议的继承
协议也可以继承一个或多个协议并在基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// 这里是协议的定义部分
}
类的专属协议
添加AnyObject
到协议的继承列表,那么这个协议只可以用在类类型,不能用在结构体或者枚举类型。
协议合成
如果说一个类型需要遵循多个协议,那么可以使用协议合成复合成同一个协议,使用SomeProtocol & AnotherProtocol
,同时协议组合也能包含类类型,这允许你标明一个需要的父类。
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)
// 打印 “Happy birthday Malcolm - you're 21!”
检查协议一致性
可以使用is
或者as
进行协议类型检查,检查是否符合某个协议。
is
检查实例是否符合某个协议,符合返回true
,否则返回false
;as?
返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回nil
;as!
将实例强制向下转换到某个协议类型,如果强转失败,将触发运行时错误。
可选的协议要求
协议可以定义可选的要求,遵循协议的类型可以选择是否实现这些要求。在协议中可选的要求使用optional
关键字作为前缀。可选要求如果用在和Object-C打交道的代码中,协议和可选要求都必须加上@obj
属性。记@objc
特性的协议只能被继承自Objective-C类的类或者@objc
类遵循,其他类以及结构体和枚举均不能遵循这种协议。
可选要求的属性可会变成可选的,即使是函数也会变成可选类型,而不是返回值可选。协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。
协议扩展
协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。这样就不需要在所有遵循该协议的类型进行重复的实现,也不用使用全局函数。
提供默认实现
可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。
为协议扩展添加限制条件
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用where
子句来描述。