本文参考了Swift2.2与3.0的语法,在少数地方添加了自我理解与示例.
协议(Protocols)
protocol 规定了 用来实现某一特定工作或者功能 所必需的 方法和属性。
类,结构体或枚举类型都可以遵循协议,并必须提供具体实现来完成协议规定的方法和功能。
任意能够满足协议要求的类型被称为这个协议的遵循者。
可以对协议进行扩展
协议的语法
协议的定义方式与类,结构体,枚举的定义非常相似。
protocol SomeProtocol { // 协议内容 }
要使类遵循某个协议,需要在类型名称后加上协议名称,中间以 : 分隔,作为类型定义的一部分。
遵循多个协议时,各协议之间用 , 分隔。
struct SomeStructure: FirstProtocol, AnotherProtocol { // 结构体内容 }
如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔。
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol { // 类的内容 }
对属性的规定
协议 可以 规定其遵循者 提供 实例属性(instance property) 或 类属性(type property)
而不用指定是存储型属性(stored property)还是计算型属性(calculate property)
此外还必须指明 属性是 只读属性 还是 读写属性。
若协议规定属性是读写属性,那这个属性不能是常量或只读的计算属性。
若协议规定属性是只读属性,如果你代码需要的话,该属性也可以是可写的。
protocol SomeProtocol { var mustBeSettable : Int { get set } //读写属性 var doesNotNeedToBeSettable: Int { get }//只读属性 }
在协议中定义类属性(type property)时,总是使用static关键字作为前缀。
当协议的遵循者是类时,定义该类时可以使用class或static关键字来声明类属性:
protocol AnotherProtocol { static var someTypeProperty: Int { get set } }
如下所示,这是一个含有一个实例属性要求的协议:
protocol FullyNamed { var fullName: String { get }//只读属性(代码需要时也可以是可写的) }
这个协议表示,任何遵循FullyNamed协议的类型,都必须提供一个只读的String类型实例属性 。
下面是一个遵循FullyNamed协议的简单结构体:
struct Person: FullyNamed//遵循FullyNamed协议 { var fullName: String } var john = Person(fullName: "John Appleseed") print(john.fullName)//可读 john.fullName = "JOHN"//可写 print(john.fullName) //John Appleseed //JOHN
这个例子中定义了一个叫做Person的结构体,用来表示具有名字的人。从第一行代码中可以看出,它遵循了协议。
Person结构体的每一个实例都有一个String类型的存储属性fullName,满足了FullyNamed协议的要求,也就是意味着,Person结构体完整的遵循了协议.
对方法的规定
协议可以要求其遵循者实现某些指定的 实例方法 或 类型方法。
这些方法作为协议的一部分放在协议的定义中,但是不需要大括号和方法体。(只声明,不需要实现)
协议中定义的方法支持可变参数 , 不支持默认参数。
在协议中定义类方法的时候,总是使用static关键字作为前缀。
当协议的遵循者是类的时候,你可以在类的实现中使用static或者class来实现协议中的类型方法:
protocol SomeProtocol { static func someTypeMethod() }
下面的例子定义了含有一个实例方法的协议:
protocol RandomNumberGenerator { func random() -> Double }
RandomNumberGenerator协议要求其遵循者必须拥有一个名为random, 返回值类型为Double的实例方法。
如下所示,下边的是一个遵循了RandomNumberGenerator协议的类。
class LinearCongruentialGenerator:RandomNumberGenerator { func random() -> Double { return 1.01 } }
对Mutating实例方法的规定
可以在协议中定义Mutating实例方法(在方法前加mutating关键字),使协议的遵循者的实例在调用该方法时能够修改实例和实例的属性。
注意
用类实现协议中的mutating方法时,不用写mutating关键字
用结构体,枚举实现协议中的mutating方法时,必须写mutating关键字。
如下所示, Togglable 协议含有名为 toggle 的实例方法。
根据名称推测, toggle() 方法将通过改变实例属性,来切换遵循该协议的实例的状态。
toggle() 方法在定义的时候,使用mutating关键字标记,这表明当它被调用时该方法将会改变协议遵循者实例
protocol Togglable { mutating func toggle() }
使用枚举或结构体来实现Togglable协议时,需要提供一个带有mutating前缀的toggle方法。
eg.
enum OnOffSwitch: Togglable { case Off, On mutating func toggle() { switch self { case Off: self = On case On: self = Off } } } var lightSwitch = OnOffSwitch.Off print(lightSwitch.hashValue) lightSwitch.toggle() print(lightSwitch.hashValue) //0 //1
使用class来实现Togglable协议时,提供的toggle方法不需要有前缀mutating。
class OnOffSwitch: Togglable { var onOrOff : Bool = false func toggle() { if self.onOrOff == false { self.onOrOff = true } else { self.onOrOff = false } } } let lightSwitch = OnOffSwitch() print(lightSwitch.onOrOff) lightSwitch.toggle() print(lightSwitch.onOrOff) //false //true
对构造器的规定
协议可以要求它的遵循者实现指定的构造器。
在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体:
protocol SomeProtocol { init(someParameter: Int) }
协议中规定的构造器在类中的实现
你可以在遵循该协议的类中实现构造器,并指定其为类的指定构造器(designated initializer)或者便利构造器(convenience initializer)。
在这两种情况下,在类的定义中 实现构造器时 必须 标上 required修饰符
(因为遵循协议,所以必须实现协议中的构造器)
eg.
class SomeClass: SomeProtocol { required init(someParameter: Int) { //构造器实现 } }
使用required修饰符可以保证:所有的遵循该协议的子类,能为构造器规定提供一个显式的实现
关于required构造器的更多内容,请参考必要构造器。
注意
如果类已经被标记为final,那么在协议构造器的实现中不需要使用required修饰符。
因为final类不能有子类。
关于final修饰符的更多内容,请参见防止重写。
如果一个子类遵循协议并需要实现协议中规定的构造器同时子类重写了父类的指定构造器,那么该构造器的实现需要被同时标示required和override修饰符:
`
protocol SomeProtocol
{
init()
}class SomeSuperClass
{
init()
{
// 构造器的实现
}
}class SomeSubClass: SomeSuperClass, SomeProtocol
{
//因为遵循协议,需要加上"required";
//因为继承自父类,需要加上"override"
required override init()
{
// 构造器实现
}
}
`
可失败构造器的规定
可以通过给协议中添加可失败构造器来使遵循该协议的类型必须实现该可失败构造器。
如果在协议中定义一个可失败构造器,则在遵循该协议的类型中必须添加同名同参数的可失败构造器或非可失败构造器。
如果在协议中定义一个非可失败构造器,则在遵循该协议的类型中必须添加同名同参数的非可失败构造器或隐式解析类型的可失败构造器( init! )。
协议可以被当做类型来使用
尽管协议本身并不实现任何功能(只声明属性和方法)
但是协议可以被当做类型来使用。
协议可以像其他普通类型一样使用,使用场景:
作为函数、方法或构造器中的参数类型或返回值类型
作为常量、变量或属性的类型
作为数组、字典或其他容器中的元素类型
注意
协议是一种类型,因此协议类型的名称应与其他类型(Int,Double,String)的写法相同,使用大写字母开头的驼峰式写法,例如(FullyNamed)
如下所示,这个示例中将协议当做类型来使用:
`
protocol SomeProtocol//协议SomeProtocol可以作为类型来使用
{
func protocolFunc() -> Double
}//类Zunxunzhe遵循SomeProtocol协议
//或者 理解为 类Zunxunzhe是SomeProtocol协议类型的子类
class Zunxunzhe: SomeProtocol
{
func protocolFunc() -> Double
{
return 3.14
}
}class SomeClass
{
//属性aProtocol的类型为SomeProtocol协议类型
//因此任何遵循了SomeProtocol协议的类型的实例都可以赋值给属性aProtocol
let aProtocol: SomeProtocol//SomeClass类中的构造器的 参数aProtocol类型 是 SomeProtocol协议类型
//在调用构造方法时创建SomeClass的实例时,可以将任何遵循 SomeProtocol协议的实例作为参数。
init(aProtocol: SomeProtocol)
{
self.aProtocol = aProtocol
}func toInt() -> Int { return Int(aProtocol.protocolFunc()) }
}
//前面代码中规定了 :
//类Zunxunzhe遵循SomeProtocol协议
//或者 理解为 类Zunxunzhe是SomeProtocol协议类型的子类
let instance = SomeClass(aProtocol: Zunxunzhe())
print(instance.toInt())
//3
`
委托模式
委托是一种设计模式,
委托模式允许类或结构体将一些需要它们负责的功能 委托给 其他的类型的实例。
委托模式的实现很简单:
定义协议来封装那些需要被委托的函数和方法
使其遵循者 拥有 这些被委托的函数和方法
从而将函数和方法实现的功能 委托给 遵循者的实例
在扩展中使已存在的类型遵循协议
即便无法修改源代码,依然可以通过扩展(Extension)来扩充已存在类型(译者注: 类,结构体,枚举等)。
扩展可以为已存在的类型添加属性,方法,下标脚本,协议等成员。
详情请在扩展章节中查看。
注意
通过扩展使已存在的类型遵循协议时,该类型的所有实例也会获得协议中的方法
在扩展的时候,协议名称写在类型名之后,以冒号隔开,在大括号内写明新添加的协议内容。
eg.
`
protocol TextRepeatable
{
func repeatText()->String
}
extension NSString : TextRepeatable
{
func repeatText()->String
{
return (self as String) + (self as String)
}
}`
通过扩展,为NSString类添加新的 成员TextRepresentable协议使得NSString类遵循了一个新的协议TextRepeatable
现在所有NSString的实例都遵循了TextRepresentable协议:
let nsstr = NSString.init(string: "abc") print(nsstr.repeatText()) //abcabc
同样NSInteger类也可以通过扩展的方式来遵循TextRepresentable协议
eg.
extension NSInteger : TextRepeatable { func repeatText()->String { return String(self) + String(self) } } let int101 = NSInteger.init(101) print(int101.repeatText()) //101101
通过扩展来补充声明类型遵循协议
当一个类型已经实现了协议中的所有要求,却没有声明遵循该协议时
可以通过空的扩展来补充声明遵循协议
eg.
struct SomeStruct { var str :String func repeatText()->String { return self.str + self.str } }
虽然结构体SomeStruct实现了协议TextRepeatable规定的方法repeatText,但是没有声明遵循该协议
所以结构体SomeStruct的类型不会自动转变为协议类型TextRepeatable
可以通过空的扩展使结构体SomeStruct补充遵循协议TextRepeatable
extension SomeStruct : TextRepeatable {}
此时SomeStruct的实例可以作为TextRepresentable类型使用
eg.
let someStruct = SomeStruct.init(str: "asdf") let someTextRepeatable : TextRepeatable = someStruct print(someTextRepeatable.repeatText()) //asdfasdf
协议类型的集合
协议类型可以在集合中使用
下面的例子创建了一个类型为[TextRepresentable]的数组:
`
let nsstr = NSString.init(string: "abc")//nsstr:NSString
let int101 = NSInteger.init(101)//int101:NSInteger
let someStruct = SomeStruct.init(str: "asdf")//someStruct:SomeStructlet things: [TextRepeatable] = [nsstr,int101,someStruct]
for thing in things//thing:TextRepeatable
{
print(thing.repeatText())
}
//abcabc
//101101
//asdfasdf
`
thing被当做是TextRepeatable类型而不是NSString,NSInteger, SomeStruct等类型,即使真实的实例是它们中的一种类型。
尽管如此,由于它是TextRepeatable类型,任何TextRepeatable都拥有实例方法repeatText,所以循环访问 thing.repeatText() 是安全的。
协议的继承
协议能够继承一个或多个其他协议,可以在继承的协议基础上增加新的协议要求。
协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol { // 协议定义 }
下例中定义了2个协议protocol1,protocol2
protocol2继承自protocol1。
任何遵循protocol2协议的类型在满足该协议的要求时,也必须满足protocol1协议的要求
eg.
protocol protocol1 { func func1() } protocol protocol2:protocol1 { func func2() } extension String:protocol2 { func func1() { print("func1 : + \(self)") } func func2() { print("func2 : + \(self)") } } let hehe = "hehe" hehe.func1() hehe.func2() //func1 : + hehe //func2 : + hehe
类专属协议
你可以在协议的继承列表中,通过添加class关键字,限制协议只能被class类型适配并遵循。
即结构体或枚举不能遵循该协议。
class关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。
`
protocol aProtocol
{
func hehe()
}//SomeClassOnlyProtocol协议只能被类(class)类型适配并遵守
//SomeClassOnlyProtocol协议继承aProtocol协议
protocol SomeClassOnlyProtocol: class,aProtocol
{
func pFunc()
}
//通过扩展使NSString类遵循协议SomeClassOnlyProtocol
extension NSString:SomeClassOnlyProtocol
{
func hehe() {
print("hehe + (self)")
}
func pFunc()
{
print("pFunc + (self)")
}
}
let str : NSString = "haha"
str.hehe()
str.pFunc()
//hehe + haha
//pFunc + haha
`
在以上例子中,协议SomeClassOnlyProtocol只能被类(class)类型适配。
如果尝试让结构体或枚举类型适配该协议,则会出现编译错误。
eg.
extension String:SomeClassOnlyProtocol //❌此处报错:Non-class type 'String' cannot conform to class protocol 'SomeClassOnlyProtocol' //因为String类型是是通过结构体实现的 { func hehe() { print("hehe + \(self)") } func pFunc() { print("pFunc + \(self)") } }
注意
当协议要求(或假设)它的遵循者类型必须是引用类型而非值类型时,应该采用类专属协议。
关于引用类型,值类型的更多内容,请查看结构体和枚举是值类型和类是引用类型。
协议合成
有时候需要同时遵循多个协议。
你可以将多个协议采用protocol
你可以在<>中罗列多个你想要遵循的协议,以逗号分隔。
下面的例子中,将 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(celebrator: protocol
) { print("Happy birthday \(celebrator.name) - you're \(celebrator.age)!") } let birthdayPerson = Person(name: "Malcolm", age: 21) wishHappyBirthday(birthdayPerson) //Happy birthday Malcolm - you're 21!
Person结构体遵循了Named协议和Aged协议
wishHappyBirthday函数的参数类型为protocol
因此任意遵循这两个协议的类型的实例都可以作为参数传入wishHappyBirthday(_:)函数。
注意
协议合成并不会生成一个新协议,而是将多个协议合成为一个临时的协议
检验是否符合协议
使用is来检查实例是否遵循某一协议
使用as来强制转换实例为某一协议类型
检查和转化的语法详情查看类型转换
is用来检查实例是否遵循了某个协议
as?返回一个可选值,当实例遵循协议时,返回该协议类型;否则返回nil。
as!用以强制向下转换,如果强转失败,会引起运行时错误。
下面的例子定义了一个HasArea的协议,要求有一个Double类型的只读属性area
protocol HasArea { var area: Double { get } }
如下所示,定义了 Circle 和 Country 类,它们都遵循HasArea协议:
`
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类把计算属性area实现为基于存储属性radius, Country类则把area实现为存储属性。
这两个类都遵循了HasArea协议。
如下所示, 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) ]
如下所示, objects数组可以被迭代,对每一个元素进行检查,看它是否遵循了HasArea协议
eg.
for object in objects { if let objectWithArea = object as? HasArea { print("Area is \(objectWithArea.area)") } else { print("Something that doesn't have an area") } } // Area is 12.5663708 // Area is 243610.0 // Something that doesn't have an area
当元素遵循HasArea协议时,通过as?操作符可以将其可选绑定到objectWithArea常量上.
objects数组中元素的类型并不会因为类型转换而丢失类型信息,它们仍然是Circle,Country,Animal类型.
对协议中可选成员的规定
协议可以含有可选成员,其遵守者可以选择是否实现这些成员。
在协议中使用optional关键字作为前缀来定义可选成员。
当需要使用可选规定的方法或者属性时,他的类型自动会变成可选的。
比如,一个定义为(Int)->String类型的方法变成( (Int)->String )?
需要注意的是整个方法定义包裹在可选中(整个方法是可选的)
协议规定的可选成员在调用时使用可控链 ,因为协议的遵循者可能没有实现可选内容。
像someOptionalMethod?(someArgument)这样,你可以在可选方法名称后加上?来检查该方法是否被实现。
详细内容在可空链式调用章节中查看。
注意
可选协议只能在含有@objc前缀的协议中生效。
这个前缀表示协议将暴露给Objective-C代码。
即使你不打算和Objective-C有什么交互,如果你想要指明协议包含可选属性,那么还是要加上@objc前缀。
@objc的协议 只能 由 继承自Objective-C类的类或者其他@objc类来遵循。
@objc的协议 也不能被结构体和枚举遵循。
下面的例子定义了一个叫Counter的整数加法类
@objc class Counter { var count = 0 var dataSource: CounterDataSource? func increment() { if let amount = dataSource?.incrementForCount?(count) { count += amount } else if let amount = dataSource?.fixedIncrement? { count += amount } } }
CounterDataSource协议中声明了两个可选成员
@objc protocol CounterDataSource { optional func incrementForCount(count: Int) -> Int //可选方法 optional var fixedIncrement: Int { get }//可选属性 }
ThreeSource类实现了CounterDataSource协议
虽然ThreeSource类只实现了可选属性fixedIncrement
`
@objc class ThreeSource: CounterDataSource
{
let fixedIncrement = 3
}var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4
{
counter.increment()
print(counter.count)
}
// 3
// 6
// 9
// 12
`
对协议进行扩展
对协议进行扩展 可以 为遵循者 提供方法或属性的默认的实现
所有协议的遵循者,在不用任何修改的情况下,都自动得到了这个扩展所增加的属性或方法的默认实现。
例如,可以扩展RandomNumberGenerator协议,为其遵循者提供默认的randomBool方法。
`
//协议RandomNumberGenerator
protocol RandomNumberGenerator
{
//协议RandomNumberGenerator声明的方法random
//需要遵循者提供方法random实现
func random()->Double
}//扩展协议RandomNumberGenerator
extension RandomNumberGenerator
{
//为遵循者提供方法randomBool默认的实现,遵循者可以自动获得
func randomBool() -> Bool
{
return random() > 0.5
}
}//Num类 遵循 协议RandomNumberGenerator
class Num: RandomNumberGenerator
{
//Num类 需要提供 RandomNumberGenerator协议的定义中规定的方法random实现
func random()->Double
{
return 3.14
}
//而Num类会自动获得RandomNumberGenerator协议的扩展中的方法randomBool的默认实现,因此不用实现
}let num = Num()
print(num.random())
print(num.randomBool())
//3.14
//true
`
如果协议的遵循者对扩展协议中的属性/方法 提供了自己的实现,那么遵循者提供的实现将被使用。
eg.
//Num类 遵循 协议RandomNumberGenerator class Num: RandomNumberGenerator { func random()->Double { return 3.14 } //Num类 对协议的扩展中的方法randomBool提供了自己的实现 func randomBool() -> Bool { print("always false") return false } } let num = Num() print(num.random()) print(num.randomBool()) //3.14 //always false //false
为扩展协议添加限制条件
在扩展协议的时候,可以指定一些限制,只有满足这些限制的协议的遵循者,才能获得协议扩展所提供的属性和方法。
这些限制写在被扩展协议名之后,使用where关键字来描述限制情况。
eg:
`
protocol TextRepresentInBig
//TextRepresentInBig协议用来作为对CollectionType协议的扩展的限制条件
{
var descriptionInBig: String { get }
}
extension CollectionType where Generator.Element : TextRepresentInBig
//通过扩展为CollectionType协议添加限制条件:只适用于集合类型中元素遵循TextRepresentInBig的情况
//只有满足限制条件的CollectionType协议的遵循者才能获得此扩展中的计算属性CollectionDescription
{
var CollectionDescription: String {
let itemsAsText = self.map { $0.descriptionInBig }
return itemsAsText.joinWithSeparator("/")
}
}
struct Text:TextRepresentInBig
//Text类型遵循协议TextRepresentInBig
{
var text: String
var descriptionInBig: String
{
return text.uppercaseString
}
}
let text1 = Text(text: "haha")
let text2 = Text(text: "HEHE")
let text3 = Text(text: "HOho")
let textArray = Array.init(arrayLiteral: text1,text2,text3)
//Array类型默认遵循协议CollectionType
//因为Array类型的实例textArray中的元素类型为Text,Text类型遵循协议TextRepresentable
//所以textArray满足限制条件:where Generator.Element,获得扩展中的计算属性CollectionDescription
print(textArray.CollectionDescription)
//HAHA/HEHE/HOHO
`
注:
如果协议有多个扩展并在多个扩展中提供了相同的方法/属性的不同的实现,而该协议的遵循者同时满足了多个扩展的限制条件,那么遵循者将会使用限制条件严格的那个扩展中的实现。
eg:
`
protocol TextRepresentInBig
{
var descriptionInBig: String { get }
}
protocol TextRepresentInDoubled
{
var descriptionInDoubled: String { get }
}extension CollectionType where Generator.Element : TextRepresentInBig
{
var CollectionDescription: String {
let itemsAsText = self.map { $0.descriptionInBig }
return itemsAsText.joinWithSeparator("/")
}
}
extension CollectionType where Generator.Element : TextRepresentInDoubled , Generator.Element : TextRepresentInBig
{
var CollectionDescription: String
{
let itemsAsText = self.map { $0.descriptionInDoubled }
return itemsAsText.joinWithSeparator("\")
}
}
struct Text:TextRepresentInBig,TextRepresentInDoubled
{
var text: String
var descriptionInBig: String
{
return text.uppercaseString
}
var descriptionInDoubled: String
{
return text + text
}
}
let text1 = Text(text: "haha")
let text2 = Text(text: "HEHE")
let text3 = Text(text: "HOho")
let textArray = Array.init(arrayLiteral: text1,text2,text3)
print(textArray.CollectionDescription)
//hahahaha\HEHEHEHE\HOhoHOho
`