协议语法、属性要求、方法要求、Mutating方法要求、构造器要求、协议作为类型、委托(代理)模式、通过扩展添加协议一致性、通过扩展遵循协议、协议类型的集合、协议的继承、类类型专属协议、协议合成、检查协议一致性、可选的协议要求、协议扩展。
协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其它需要的东西。类、结构体或枚举都可以遵循协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型遵循这个协议。
除了遵循协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样遵循协议的类型就能够使用这些功能。
协议语法:
协议的定义方式与类、结构体和枚举的定义非常相似:
protocol SomeProtocol{
//定义部分
}
要让自定义类型遵循某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(:)分隔。遵循多个协议时,各协议之间用逗号分隔。
struct SomeStruct: FirstProtocol, SecondProtocol{
//
}
拥有父类的类在遵循协议时,应该将父类名放在协议名之前,以逗号分隔:
class SomeClass: SomeSuperClass, FirstProtocol, SecondProtocol
//
}
属性要求:
协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储属性还是计算型属性,它只指定属性的名称和类型。此外,协议还指定属性是可读的还是可读可写的。
协议总是用var关键字来声明变量属性,在类型声明后加上 {set get }来表示属性是可读可写的,可读属性则用 {get} 来表示:
protocol SomeProtocol{
var mustBeSettable:Int { get set }
var doesNotNeedToBeSettable:Int { get }
}
在协议中定义类型属性时,总是使用static关键字作为前缀。当类类型遵循协议时,除了static关键字,还可以使用class关键字来声明类型属性:(类型属性是属于类而不是实例的属性)
protocol AnotherProtocol{
static var someTypeProperty:Int { get set }
}
方法要求:
协议可以要求遵循协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体。可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法的参数提供默认值。
在协议中定义类方法的时候,总是使用static关键字作为前缀。当类类型遵循协议时,除了static关键字,还可以使用class关键字作为前缀。
protocol SomeProtocol{
static func someTypeMethod()->Void
}
Mutating方法要求:
有时需要在方法中改变方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将mutating关键字作为方法的前缀,写在func关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。
如果你在协议中定义了一个实例方法,该方法会改变遵循该协议的类型的实例,那么在定义协议时需要在方法前加mutating关键字。这使得结构体和枚举能够遵循此协议并满足此方法要求。
注意:实现协议中的mutating方法时,若是类类型,则不用写mutating关键字。而对于结构体和枚举,则必须写mutating关键字。
protocol P{
mutating func chg()->Void
}
struct S:P{
var name:Strng
mutating func chg()->Void{
self.name="cjj"
}
}
class C:P{
var name:String?
func chg()->Void{
self.name="cjj"
}
}
类是引用类型,结构体枚举是值类型,值类型的方法默认是不能对本身实例进行写操作的,需要加上mutating
构造器要求:
协议可以要求遵循协议的类型实现指定的构造器。像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体。
protocol SomeProtocol{
init(someParameter:Int)
}
构造器要求在类中的实现:
可以在遵循协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,都必须为构造器实现标上required修饰符。
class SomeClass:SomeProtocol{
required init(someParameter:Int){
//实现部分
}
}
使用required修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。
注意:如果类已经被标记为final,那么不需要在协议构造器的实现中使用required修饰符,因为final类不能有子类。
如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注required和override修饰符。
class SomeSubClass:SomeSuperClass, SomeProtocol{
required override init(){
//
}
}
可失败构造器要求:
协议还可以为遵循协议的类型定义可失败构造器要求。
遵循协议的类型可以通过可失败构造器(init?)或非可失败构造器(init)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器或隐式解包可失败构造器(init!)来满足。
协议作为类型:
尽管协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用。
协议可以像其它普通类型一样使用,使用场景如下:
1)作为函数、方法或构造器中的参数类型或返回值类型
2)作为常量、变量或属性的类型
3)作为数组、字典或其它容器中的元素类型
注意:协议是一种类型,因此协议类型的名称应与其它类型(例如Int, Double, String)的写法相同,使用大写字母开头的驼峰式写法。
protocol P{
...
}
class C{
...
var a:P
init(_ b:P){
self.a=b
}
}
这里P是一个协议,C是一个类,C类中有属性a是协议P类型,这表示可以将遵循了P协议的任意类型通过构造器赋值给a。
委托(代理)模式:
委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其它类型的实例。委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保遵循协议的类型能提供这些功能,委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需担心外部数据源的类型。
通过扩展添加协议一致性:
即便无法修改源代码,依然可以通过扩展令已有类型遵循并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。
注意:通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。
protocol TextRepresentable{
var textualDescription:String { get }
}
extension Dice:TextRepresentable{
var textualDescription:String{
return "...."
}
}
通过扩展遵循并符合协议,和在原始定义中遵循并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容1。
通过扩展遵循协议:
当一个类型已经符合了某个协议中的所有要求,却还没有声明遵循该协议时,可以通过空扩展体的扩展来遵循该协议:
struct Hamster{
var name:String
var textualDescription:String{
return "..."
}
}
extension Hamster:TextRepresentable {}
即使满足了协议的所有要求,类型也不会自动遵循该协议,必须显式地遵循协议。
协议类型的集合:
协议类型可以在数组或者字典这样的集合中使用。
let things:[TextRepresentable]=[game,d12,simonTheHamster]
things数组是一个协议类型,它的实际存储数据可以是任意遵循该协议的某个类型。
协议的继承:
协议能够继承一个或多个其它协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用盗号分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol{
//
}
类类型专属协议:
可以在协议的继承列表中,通过添加class关键字来限制协议只能被类类型遵循,而结构体或枚举不能遵循该协议。class关键字必须第一个出现在协议的继承列表中,在其它继承的协议之前:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol{
//
}
//这表示协议SomeClassOnlyProtocol只能被类类型继承,否则会编译出错
当写已定义的要求需要遵循协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。
协议合成:
可以将多个协议采用 ProtocolA & ProtocolB 这样的格式进行组合,称为协议合成。以符号(&)分隔。
组合后的协议可以表示一个数据的类型
var a:ProtocolA & ProtocolB
这表示a是两个协议类型,可以用同时遵循了这两个协议的类型实例进行赋值。
协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求合成到一个只在局部作用域有效的临时协议中。
检查协议一致性:
可以使用类型转换中描述的 is 和 as 操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查和转换到某个协议类型在语法上和类型的检查和转换完全相同。
is用来检查实例是否符合某个协议,若符合返回true,否则返回false
as?返回一个可选值,当实例符合某个协议时,返回类型为1协议类型的可选值,否则返回nil
as!将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误。
可选的协议要求:
协议可以定义可选要求,遵循协议的类型可以选择是否实现这些要求。在协议中使用optional关键字作为前缀来定义可选要求。可选要求用在需要和OC打交道的代码中。
协议和可选要求都必须带上@ojbc属性。标记@ojbc特性的协议只能被继承自OC类的类或者@ojbc类遵循,其它类以及结构体和枚举均不能遵循这种协议。
使用可选要求时(可选方法或属性),它们的类型会自动变成可选的。比如,一个类型为(Int)->String的方法会变成 ((Int) ->String)?。整个函数类型是可选的,而不是函数的返回值。
协议中的可选要求可通过可选链式调用来使用,因为遵循协议的类型可能没有实现这些可选要求。
@ojbc protocol CounterDataSource{
optional func incrementForCount(count:Int)->Int
optional var fixedIncrement:Int { get }
}
严格来说,CounterDataSource协议中的方法和属性都是可选的,因此遵循协议的类可以不实现这些要求,尽管技术上允许这样做,不过最好不要这样写。
协议扩展:
协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。通过这种方式,可以基于协议本身来实现这些功能,而无需在每个遵循协议的类型中都重复同样的实现,也无需使用全局函数。
RandomNumberGenerator是一个协议。
extension RandomNumberGenerator{
func randomBool() -> Bool{
return random() > 0.5
}
}
扩展协议,在扩展体中实现扩展的具体内容。
提供默认实现:
可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果遵循协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。
通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,遵循协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。
为协议扩展添加限制条件:
在扩展协议的时候,可以指定一些限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用 where 子句来描述。
extension CollectionType where Generator.Element:TextRepresentable{
var ....
}
如果多个协议扩展都为同一个协议要求提供了默认实现,而遵循协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实现。