协议,就是一些规定。当一个类遵循了协议之后,它就必须满足协议所规定的内容。
使用协议的好处就是,以后我们可以面向协议编程。我们写的同一段代码可以针对不同的类型使用,只要这个类型遵循了我们所定义的协议。
协议定义语法
protocol SomeProtocol {
// protocol definition goes here
}
类型实现协议的语法。类型可以是类、结构体、枚举类。协议写在父类的后面,用逗号分开。如果有多个协议,也用逗号分开。
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
// class definition goes here
}
协议里面可以定义一些实例属性或类型属性,那么遵循这个协议的类型就必须提供这些属性。可以是用存储属性或计算属性提供。
假如协议里面定义的属性是可以读写的。那么类型必须提供可读写的属性。如果协议定义只读的,那么类型里面可以是读写的。
protocol FruitProtocol {
static var name: String {get}
var price: Int {set get} //set 和 get 没顺序要求
}
class Apple: FruitProtocol {
static var name: String = "apple"
var price: Int {
get {
return 10
}
set {
print("I don't want to change the price!!")
}
}
}
方法要求
protocol SomeProtocol {
static func someTypeFunc()
func someFunc()
mutating func someMutatingFunc()
}
例子如上,协议里面的方法不用写方法体。如果是类型方法,加上关键字static。类在实现类型方法的时候,可以用class或static修饰。
注意最后一个实例方法,它加上了mutating关键字。这个主要是给值类型使用的。因为如果值类型在方法里面修改属性的话,必须用到mutating去修饰方法。如果是类实现这个协议的话,那么这个方法不用加mutating关键字。
构造器要求
protocol SomeProtocol {
init(someParameter: Double)
init?(someParameter: Int)
}
当一个类实现这种协议的时候,它如果不是一个final类(不允许继承),那么它在实现这些构造器的时候,构造器必须加上required关键字。
如果一个类实现协议的构造器刚好和父类的构造器是一样的,那么要同时加上required和override关键字。
Swift中的协议可以当做类型来使用
把协议当做类型使用,打个比方,就是把协议当做Int使用。Int能使用的地方,协议都可以使用。比如定义一个协议类型的变量。用协议类型的变量做函数的参数,返回值。
这里的用法和java是一样的。和OC不同。OC是用id<SomeProtocol>来代表一个实现协议的类型。
protocol SomeProtocol {
init(someParameter: Double)
init?(someParameter: Int)
}
var p: SomeProtocol? = nil
func someFunc(p: SomeProtocol) -> SomeProtocol {
return p
}
利用扩展来实现协议
假如我们已经有了一个Animal类,但是不能获取到它的源代码。可以我们又想要它去遵守一定的协议,这时候可以用扩展为其添加协议。但是因为是扩展,所以还是不给添加存储属性。这时候必须用计算属性来实现协议。下面我们让Animal类实现上面的FruitProtocol协议。
extension Animal: FruitProtocol {
var price: Int {
get{
return 23434
}
set {
print("do nothing")
}
}
static var name: String {
return "animal"
}
}
如果一个类在定义的时候已经满足某个协议的所有需求,那么可以使用extension显式指明它遵守了这个协议。这个时候,extension里面不用写什么。另外如果一个类满足了某个协议的需求,你不显式声明它遵守这个协议的话,它是不实现这个协议的。
继承协议
协议和类一样,是可以继承的。继承后的协议可以再添加一些属性和方法。如果需要继承多个协议,那么就用逗号分开多个协议。
protocol AnimalProtocol: FruitProtocol, SecondProtocol{
var canRun: Bool {get}
func makeSounds()
}
Class协议
如果你的协议只想用在类里面,那么可以声明它为一个Class协议。这种协议不能被值类型实现。语法就是在继承协议的冒号后面加上class关键字。没继承协议的话,后面的协议就不用写了。但是class关键字一定要写在被继承协议的前面。
protocol AnimalProtocol:class, FruitProtocol, SecondProtocol{
var canRun: Bool {get}
func makeSounds()
}
遵守多个协议
上面提到了,协议可以看成是一种类型,然后可以充当参数,返回值等等。但是有些时候想要一个参数同时满足多个协议。这个时候就不能用单个协议去充当参数了。这时候可以用一对<>号将多个协议连接起来。例子如下
func doSomething(para: protocol<FruitProtocol,SecondProtocol>) {
//do something
}
这时候,para参数就必须同时遵守这两个协议才行。
判断实例是否遵守协议
is,这个is用于判断某个实例是否遵守了某个协议。
as?和as!。这个和类转换一样,用于将一个实例转换为某个协议类。如果这个实例遵守了这个协议,那么它就能被转换。
可选需求
上面所讲的所有定义在协议里面的东西都是必须实现的。但是有些情况下,我们想这些需求不一定要实现,而是可选地实现。这个时候就要用到可选需求。这个概念应该是直接利用了OC里面的概念来使用的。语法有两处:一处是@objc,一处是optional。
@objc protocol SecondProtocol {
optional var time: Double {get}
optional func doHomework()
}
关键字optional用来定义这个需求是不是可选的。
@objc一般是用于Swift和OC的混合编程。但是这里即使你没有使用到混合编程,如果想要实现可选需求,还是必须加上@objc。
使用到可选需求的时候要注意一点奇怪的事情。
第一,实现带有@objc的协议的时候,实现的内容上必须加上@objc。否则报错。这个@objc可以加在实现的属性和方法上,也可以加在类的声明关键字的最前面,但是后一种方法要求这个类必须是NSObject的子类。
第二,如果是在类扩展里面实现带有@objc的协议,只能在实现的内容上面加上@objc。
第三条,估计是个bug,当你定义一个继承自可选协议A的协议B。然后让类C实现这个协议B,用is判断C的实例是否是A的类型,照理来讲应该是true。可是这里会出现runtime Error。
扩展协议
这个和扩展类是一样的。但是这里并不是协议,里面的属性和方法都必须按照扩展的要求来写:方法要有方法体,不能有存储属性。协议扩展里的东西对实现了该协议的类都会有扩展作用。
extension AnimalProtocol {
var canFly: Bool {
get{
return false
}
}
}
注意,如果你用协议扩展对协议的必须方法或属性做了默认的定义。那么类在实现这个协议的时候,不必实现已经实现的协议要求。但如果实现了,协议扩展里面实现的内容都会被类里面的定义覆盖。
Swift仍需改进。