Swift(二十四)协议
Protocol(协议)用于统一方法和属性的名称,而不实现任何功能。协议能够被类,枚举,结构体实现,满足协议要求的类,枚举,结构体被称为协议的遵循者。
1.协议语法
2.属性要求
3.方法要求
4.突变方法要求
5.初始化要求
6.协议类型
7.委托
8.给协议添加扩展
9.协议类型的集合
10.协议的继承
11.只有类才能使用的协议
12.协议组合
13.检查协议一致性
14.可选协议要求
15协议扩展
1.协议的语法
协议的定义与类,结构体,枚举的定义非常相似,如下所示:
protocol SomeProtocol {
// protocol definition goes here
}
在类,结构体,枚举的名称后加上协议名称,中间以冒号:分隔即可实现协议;实现多个协议时,各协议之间用逗号,分隔,如下所示:
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 结构体内容
}
2.属性要求
协议能够要求其遵循者必须含有一些特定名称和类型的实例属性(instance property)或类属性 (type property),也能够要求属性的(设置权限)settable 和(访问权限)gettable,但它不要求属性是存储型属性(stored property)还是计算型属性(calculate property)。
当某个类含有父类的同时并实现了协议,应当把父类放在所有的协议之前,如下所示:
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 类的内容
}
通常前置var关键字将属性声明为变量。在属性声明后写上{ get set }表示属性为可读写的。{ get }用来表示属性为可读的。即使你为可读的属性实现了setter方法,它也不会出错。
protocol SomeProtocol {
var musBeSettable : Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
protocol AnotherProtocol {
static var someTypeProperty: Int { get set }
}
protocol FullyNamed {
var fullName: String { get }
}
struct Person: FullyNamed {
var fullName: String
}
let john = Person(fullName: "John Appleseed")
这个例子定义了一个名为结构Person,它代表一个特定的人。Person结构体含有一个名为fullName的存储型属性,完整的遵循了协议。(若协议未被完整遵循,编译时则会报错)。
下面是一个更复杂的类,它也采用了和符合FullyNamed协议:
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")
print(ncc1701.fullName)
// ncc1701.fullName is "USS Enterprise"
Starship类将fullName实现为可读的计算型属性。它的每一个实例都有一个名为name的必备属性和一个名为prefix的可选属性。 当prefix存在时,将prefix插入到name之前来为Starship构建fullName。
3.方法要求
协议能够要求其遵循者必备某些特定的实例方法和类方法。协议方法的声明与普通方法声明相似,但它不需要方法内容。
protocol SomeProtocol {
static func someTypeMethod()
}
下面的例子定义了一个实例方法要求的协议:
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()
print("Here's a random number: \(generator.random())")
// Prints "Here's a random number: 0.37464991998171"
print("And another one: \(generator.random())")
// Prints "And another one: 0.729023776863283"
4.突变方法要求
能在方法或函数内部改变实例类型的方法称为突变方法。在值类型(Value Type)(译者注:特指结构体和枚举)中的的函数前缀加上mutating关键字来表示该函数允许改变该实例和其属性的类型。
注意:用class实现协议中的mutating方法时,不用写mutating关键字;用结构体,枚举实现协议中的mutating方法时,必须写mutating关键字。
如下所示,Togglable协议含有toggle函数。根据函数名称推测,toggle可能用于切换或恢复某个属性的状态。mutating关键字表示它为突变方法:
protocol Togglable {
mutating func toggle()
}
当使用枚举或结构体来实现Togglabl协议时,必须在toggle方法前加上mutating关键字。
如下所示,OnOffSwitch枚举遵循了Togglable协议,On,Off两个成员用于表示当前状态
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()
5.初始化要求
协议可以要求通过符合类型实现特定的初始化。你写这些初始化在完全相同的方式为正常初始化协议定义的一部分,但没有花括号或初始化体:
protocol SomeProtocol {
init(someParameter: Int)
}
类实现协议的初始化要求
如果该类接受了协议,你可以实现协议的初始化方法,但是此时需要使用关键字required来标记
class SomeClass: SomeProtocol {
required init(someParameter: Int) {
// initializer implementation goes here
}
}
注意: 你并不需要标记协议初始化实现与required上标有类修改final修改,因为最终的类不能被继承。
如果一个子类实现了父类的初始化方法,但同事也实现了协议的初始化方法,则使用关键字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
}
}
6.协议类型
协议本身实际上没有实现任何功能。但是,您创建的任何协议将成为一个不折不扣的类型,在代码中使用。
因为它是一个类型,可以使用在许多地方使用。
使用场景:
- 作为函数,方法或构造器中的参数类型,返回值类型
- 作为常量,变量,属性的类型
- 作为数组,字典或其他容器中的元素类型
这里是作为一个类型的协议的一个例子:
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
7.委托
委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能交由(委托)给其他的类型。
委托模式的实现很简单: 定义协议来封装那些需要被委托的函数和方法, 使其遵循者拥有这些被委托的函数和方法。
具体使用官方例子比较长,请自行查阅
8.给协议添加扩展
protocol TextRepresentable {
var textualDescription: String { get }
}
本着软件设计的开闭原则,对扩展开放,对修改关闭,我们可以对协议进行扩展,而添加新的方法
extension TextRepresentable {
//扩展的新内容
}
也可以扩展一个类,结构体使其遵循新的协议
extension Dice: TextRepresentable {
var textualDescription: String {
return "A \(sides)-sided dice"
}
}
通过延展补充协议声明
当一个类型已经实现了协议中的所有要求,却没有声明时,可以通过扩展来补充协议声明:
struct Hamster {
var name: String
func asText() -> String {
return "A hamster named \(name)"
}
}
extension Hamster: TextRepresentabl {}
注意:即时满足了协议的所有要求,类型也不会自动转变,因此你必须为它做出明显的协议声明
9.协议类型的集合
协议类型可以被集合使用,表示集合中的元素均为协议类型:
protocol oneProtocol {
func test()
}
class oneClass: oneProtocol {
func test() {
print("oneClass")
}
}
let one = oneClass();
class twoClass: oneProtocol {
func test() {
print("twoClass")
}
}
let two = twoClass();
//该集合里面的元素全部是遵从oneProtocol协议的对象
let things: [oneProtocol] = [one, two]
for thing in things {
print(thing.test())
}
10.协议的继承
协议能够继承一到多个其他协议。语法与类的继承相似,多个协议间用逗号,分隔
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// protocol definition goes here
}
继承该协议,同时,也要实现该协议的方法
protocol inheritProtocol {
func inherit()
}
//oneProtocol继承协议inheritProtocol, 接受oneProtocol协议的类同时也要接受inheritProtocol
protocol oneProtocol:inheritProtocol {
func test()
}
class oneClass: oneProtocol {
//inheritProtocol
func test() {
print("oneClass")
}
//inheritProtocol
func inherit() {
}
}
11.只有类才能使用的协议
就是指这个协议已经被特殊限制,只能有类来实现,结构体或者枚举都不能使用
protocol oneProtocol {
}
//使用关键字class来声明,如果该协议需要继承其他协议,需要写在其他协议最前面
protocol SomeClassOnlyProtocol: class, oneProtocol {
// class-only protocol definition goes here
}
class classA: SomeClassOnlyProtocol {
}
struct structA: SomeClassOnlyProtocol {
// 报错error: non-class type 'structA' cannot conform to class protocol 'SomeClassOnlyProtocol'
}
12.协议组合
当一个类,结构体需要同时符合多个协议时可以使用协议组合
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
struct Person: Named, Aged {
var name: String
var age: Int
}
//参数必须同时符合Named和Aged协议,中间用&链接
func wishHappyBirthday(celebrator: Named & Aged) {
print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}
let birthdayPerson = Person(name: "Malcolm", age: 21)
//Person已经遵循了这两个协议
wishHappyBirthday(celebrator: birthdayPerson)
// Prints "Happy birthday, Malcolm, you're 21!"
注意:协议合成并不会生成一个新协议类型,而是将多个协议合成为一个临时的协议,超出范围后立即失效。
13.检验协议的一致性
使用is检验协议一致性,使用as将协议类型向下转换(downcast)为的其他协议类型。检验与转换的语法和之前相同(详情查看类型检查):
- is操作符用来检查实例是否遵循了某个协议。
- as?返回一个可选值,当实例遵循协议时,返回该协议类型;否则返回nil
3.as! 强制解析,如果可选值为nil,会产生运行时错误
14.可选协议要求
15协议扩展
//协议作为类型使用
protocol RandomGenerable {
func randomNumber() -> Int
}
struct RandomNumber : RandomGenerable {
func randomNumber() -> Int {
return 100
}
}
class TenRandomNumber : RandomGenerable {
func randomNumber() -> Int {
return 6
}
}
struct Dice {
var side : Int
var randomPro : RandomGenerable
func play() -> Int {
return self.randomPro.randomNumber()
}
}
let aDice = Dice(side: 6, randomPro: TenRandomNumber())
aDice.play()