本篇將詳細(xì)總結(jié)介紹Swift協(xié)議的用法;
協(xié)議是定義一些規(guī)范(屬性、功能方法),然后由類、結(jié)構(gòu)體或者枚舉遵循并實(shí)現(xiàn)這些規(guī)范,這一過(guò)程被稱為遵循了協(xié)議。
主要內(nèi)容:
1.協(xié)議的基本語(yǔ)法
2.定義協(xié)議與實(shí)現(xiàn)協(xié)議
3.協(xié)議與構(gòu)造器
4.協(xié)議作為類型
5.協(xié)議實(shí)現(xiàn)委托代理模式
6.通過(guò)擴(kuò)展遵循協(xié)議
7.協(xié)議類型的集合
8.協(xié)議繼承協(xié)議
9.類類型專屬協(xié)議
10.協(xié)議合成
11.檢查協(xié)議的一致性
12.協(xié)議的可選性
13.協(xié)議擴(kuò)展
一、協(xié)議的基本語(yǔ)法
下面是協(xié)議的一些基本語(yǔ)法:
1.定義一個(gè)協(xié)議
protocol SomeProtocol {
//這里是協(xié)議的定義部分
}
protocol AnotherProtocol{
//這里是協(xié)議的定義部分
}
2.自定義類型遵循協(xié)議使用冒號(hào),遵循多個(gè)協(xié)議時(shí),各協(xié)議間使用逗號(hào)分隔
struct SomeStructure: SomeProtocol, AnotherProtocol {
//這里是結(jié)構(gòu)體的定義部分
}
3.擁有父類的類在遵循協(xié)議時(shí),需要將父類名放在協(xié)議名之前,以逗號(hào)分隔
class SomeClass: SomeSuperClass, SomeProtocol, AnotherProtocol {
//這里是類的定義部分
}
二、定義協(xié)議與實(shí)現(xiàn)協(xié)議
協(xié)議可以要求遵循協(xié)議的類型提供特定的屬性、方法,構(gòu)造器。如果協(xié)議中的屬性和方法沒有實(shí)現(xiàn),就會(huì)報(bào)錯(cuò);除此之外,我們還需要注意一些具體的使用規(guī)則如下:
屬性要求:
1.協(xié)議可以定義實(shí)例屬性和類型屬性(使用static);
2.協(xié)議不指定屬性是存儲(chǔ)屬性還是計(jì)算型屬性,只指定屬性名稱和類型以及讀寫性;
3.協(xié)議指定屬性的讀取類型,使用的get和set,中間不能使用逗號(hào);
4.協(xié)議總是使用var關(guān)鍵字來(lái)聲明變量屬性;
5.不能給協(xié)議屬性設(shè)置默認(rèn)值,因?yàn)槟J(rèn)值被看做是一種實(shí)現(xiàn);
方法要求:
1.協(xié)議可以定義實(shí)例方法和類方法(使用static);
2.協(xié)議定義函數(shù)時(shí)不能添加函數(shù)的實(shí)現(xiàn),同時(shí),傳入的參數(shù)也不能使用默認(rèn)參數(shù);
3.如果協(xié)議定義的實(shí)例方法會(huì)改變實(shí)例本身,需要在定義的方法名前使用mutating;這使得結(jié)構(gòu)體和枚舉能夠遵循此協(xié)議并滿足此方法要求。
下面具體演示一個(gè)協(xié)議的使用:
protocol PersonProtocol{
//1.定義屬性
static var personCount: Int {get}
var name:String{
get
set
}
var nickName:String{get set} //要求可讀可寫,則該屬性不能是常量屬性或者只讀的計(jì)算型屬性
var birthPlace:String{get} //只要求可讀,若代碼需要,實(shí)現(xiàn)時(shí)也是可寫的
var age:Int{get}
//2.定義函數(shù)
static func play()
func eat(food:String)
//func fed(food:string = "defaultfood”) 錯(cuò)誤,不能使用默認(rèn)參數(shù)
mutating func changeNickName(newName:String)
}
struct Student:PersonProtocol{
static var personCount = 0 //類屬性
var name: String = ""
var nickName: String = "" //這種形式的聲明就代表可讀可寫
let birthPlace: String = "beijing" //將只讀屬性設(shè)置為let,在合適位置給其設(shè)置默認(rèn)值就好了
var age:Int = 10
//其實(shí),只讀類型的屬性也可以設(shè)置為var,這相當(dāng)于是對(duì)其進(jìn)行擴(kuò)展,不僅遵循了原來(lái)的get,還增加了set
static func play() {
//類方法
}
func eat(food: String) {
//普通實(shí)例方法
}
mutating func changeNickName(newName: String) {
//實(shí)例方法中修改了實(shí)例屬性
self.nickName = newName
}
}
//測(cè)試代碼:
var student:Student = Student()
student.age = 18
var stu:PersonProtocol = student //這里協(xié)議也當(dāng)做了一種類型來(lái)使用,但是具體的實(shí)現(xiàn)還是是Dog完成的
//stu.age = 10 //這里報(bào)錯(cuò),因?yàn)閰f(xié)議中的age是只讀的
注意:實(shí)現(xiàn)協(xié)議中的 mutating 方法時(shí),若是類類型,則不用寫 mutating 關(guān)鍵字。而對(duì)于結(jié)構(gòu)體和枚舉,則必須寫 mutating 關(guān)鍵字。
三、協(xié)議與構(gòu)造器
這里主要總結(jié)協(xié)議在定義構(gòu)造器時(shí)候的一些要求,主要有如下幾個(gè)方面:
1.協(xié)議中可設(shè)置指定或者便利構(gòu)造器,實(shí)現(xiàn)時(shí)都需要添加required修飾符,因?yàn)檫@樣可以確保所有子類也必須提供此構(gòu)造器,從而符合協(xié)議,但是如果為final類,就不需要;
2.如果一個(gè)子類重寫了父類的指定構(gòu)造器,并且該構(gòu)造器滿足了某個(gè)協(xié)議的要求,那么該構(gòu)造器的實(shí)現(xiàn)需要同時(shí)標(biāo)注 required 和 override 修飾符;
3.協(xié)議中可定義可失敗構(gòu)造器(init?)、非可失敗構(gòu)造器(init)、隱式解包可失敗構(gòu)造器(init!);
下面是協(xié)議與構(gòu)造器使用的相關(guān)示例:
protocol Protocol {
init()
}
class SomeSuperClass {
init() {
// 這里是構(gòu)造器的實(shí)現(xiàn)部分
}
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
// 因?yàn)樽裱瓍f(xié)議,需要加上 required
// 因?yàn)槔^承自父類,需要加上 override
required override init() {
// 這里是構(gòu)造器的實(shí)現(xiàn)部分
}
}
四、協(xié)議作為類型
協(xié)議雖本身并未實(shí)現(xiàn)任何功能,但是仍然可以像其他普通類型一樣使用,如Int、Double等。協(xié)議作為類型使用的場(chǎng)景如下:
- 作為函數(shù)、方法或構(gòu)造器中的參數(shù)類型或返回值類型
- 作為常量、變量或?qū)傩缘念愋?/li>
- 作為數(shù)組、字典或其他容器中的元素類型
下面演示協(xié)議類型的使用:
//協(xié)議:定義了生成隨機(jī)數(shù)方法
protocol RandomNumberGenerator {
func random() -> Double
}
//實(shí)現(xiàn)了RandomNumberGenerator協(xié)議的類
class LinearCongruentialGenerator: RandomNumberGenerator {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
//使用truncatingRemainder方法進(jìn)行浮點(diǎn)數(shù)取余
lastRandom = (lastRandom * a + c).truncatingRemainder(dividingBy: m)
return lastRandom / m
}
}
//Dice的generator屬性,其類型是RandomNumberGenerator協(xié)議類型
class Dice {
let sides: Int
let generator: RandomNumberGenerator //協(xié)議作為屬性
//協(xié)議作為參數(shù)類型
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
五、協(xié)議實(shí)現(xiàn)委托代理模式
委托是一種設(shè)計(jì)模式,它允許類或結(jié)構(gòu)體將一些需要它們負(fù)責(zé)的功能委托給其他類型的實(shí)例。
委托模式的作用:
用來(lái)響應(yīng)特定的動(dòng)作,或者接收外部數(shù)據(jù)源提供的數(shù)據(jù),而無(wú)需關(guān)心外部數(shù)據(jù)源的類型。
委托模式的原理:
定義協(xié)議來(lái)封裝那些需要被委托的功能,這樣就能確保遵循協(xié)議的類型能提供這些功能。
下面例子演示了通過(guò)協(xié)議實(shí)現(xiàn)代理模式:
//播放音樂的協(xié)議
protocol PlayMusicTools{
func playMusic();
}
//實(shí)現(xiàn)協(xié)議的類
class QQMusisApp:PlayMusicTools{
func playMusic() {
print(“播放一首美妙的音樂")
}
}
class Person{
var delegate:PlayMusicTools?
func listenMusic(){
self.delegate?.playMusic()
}
}
//人想聽音樂但是又不能自己播放,就調(diào)用了代理的方法
let person:Person = Person()
person.delegate = QQMusisApp()
person.listenMusic()
六、通過(guò)擴(kuò)展遵循協(xié)議
我們知道,擴(kuò)展可以為已有類型添加屬性、方法、下標(biāo)以及構(gòu)造器。同樣道理,我們也可以通過(guò)擴(kuò)展為已有類型實(shí)現(xiàn)需要遵循的協(xié)議,通過(guò)這種方法與在原始定義中遵循并實(shí)現(xiàn)協(xié)議效果完全相同。
6.1.通過(guò)擴(kuò)展實(shí)現(xiàn)協(xié)議
//協(xié)議:定義一個(gè)可以打印UIView屬性fame的方法
protocol ViewProperty{
func printFrame()
}
//通過(guò)擴(kuò)展為UIView實(shí)現(xiàn)了ViewProperty協(xié)議
extension UIView:ViewProperty{
func printFrame() {
print(self.frame)
}
}
let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.printFrame()
6.2.已經(jīng)符合協(xié)議的類
如果一個(gè)類型已經(jīng)符合了某個(gè)協(xié)議的所有要求,卻還沒有聲明遵循這個(gè)協(xié)議,那么可以通過(guò)空擴(kuò)展來(lái)遵循協(xié)議。
class CustomObject{
func printFrame() {
print("this is not a view,cant print frame")
}
}
//空擴(kuò)展表示遵循協(xié)議
extension CustomObject:ViewProperty{}
//遵循了協(xié)議之后,就可以使用協(xié)議作為類型
let customOject:ViewProperty = CustomObject()
customOject.printFrame()
七、協(xié)議類型的集合
協(xié)議類型可以在數(shù)組或者字典這樣的集合中使用;如下,等號(hào)左邊的數(shù)組表示遵循了ViewProperty協(xié)議的對(duì)象構(gòu)成的數(shù)組。
let things:[ViewProperty] = [view,customOject]
for thing in things{
thing.printFrame()
}
//打印結(jié)果:
//(0.0, 0.0, 100.0, 100.0)
//this is not a view,cant print frame
八、協(xié)議繼承協(xié)議
協(xié)議繼承協(xié)議具有以下特點(diǎn):
1.協(xié)議能夠繼承一個(gè)或多個(gè)其他協(xié)議,可以在繼承的協(xié)議的基礎(chǔ)上增加新的要求。
2.協(xié)議的繼承語(yǔ)法與類的繼承相似,多個(gè)被繼承的協(xié)議間用逗號(hào)分隔:
3.所有遵循新協(xié)議的類型,也同時(shí)滿足新協(xié)議所繼承的父協(xié)議
協(xié)議繼承協(xié)議的格式如下:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// 這里是協(xié)議的定義部分
}
九、類類型專屬協(xié)議
協(xié)議的繼承列表中,通過(guò)添加 class 關(guān)鍵字來(lái)限制協(xié)議只能被類類型遵循,而結(jié)構(gòu)體或枚舉不能遵循該協(xié)議。class 關(guān)鍵字必須第一個(gè)出現(xiàn)在協(xié)議的繼承列表中,在其他繼承的協(xié)議之前。
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 這里是類類型專屬協(xié)議的定義部分
//class 關(guān)鍵字必須第一個(gè)出現(xiàn)在協(xié)議的繼承列表中,在其他繼承的協(xié)議之前
}
十、協(xié)議合成
有時(shí)候需要同時(shí)遵循多個(gè)協(xié)議,你可以將多個(gè)協(xié)議采用SomeProtocol & AnotherProtocol這樣的格式進(jìn)行組合,稱為協(xié)議合成(protocol composition);你可以羅列任意多個(gè)你想要遵循的協(xié)議,以與符號(hào)(&)分隔。
下面的例子中,將 Named 和 Aged 兩個(gè)協(xié)議按照上述語(yǔ)法組合成一個(gè)協(xié)議,作為函數(shù)參數(shù)的類型:
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
//Person遵循兩個(gè)協(xié)議
struct Person: Named, Aged {
var name: String
var age: Int
}
//函數(shù)參數(shù)celebrator的類型為 Name & Aged;
//這意味著它不關(guān)心參數(shù)的具體類型,只要參數(shù)符合這兩個(gè)協(xié)議即可;
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!”
注意:協(xié)議合成并不會(huì)生成新的、永久的協(xié)議類型,而是將多個(gè)協(xié)議中的要求合成到一個(gè)只在局部作用域有效的臨時(shí)協(xié)議中
十一、檢查協(xié)議的一致性
類型轉(zhuǎn)換中描述的is和as操作符同樣可以用來(lái)檢查協(xié)議一致性,即是否符合某協(xié)議,并且可以轉(zhuǎn)換到指定的協(xié)議類型。檢查和轉(zhuǎn)換到某個(gè)協(xié)議類型在語(yǔ)法上和類型的檢查和轉(zhuǎn)換完全相同:
is 用來(lái)檢查實(shí)例是否符合某個(gè)協(xié)議,若符合則返回 true,否則返回 false。
as? 返回一個(gè)可選值,當(dāng)實(shí)例符合某個(gè)協(xié)議時(shí),返回類型為協(xié)議類型的可選值,否則返回 nil。
as! 將實(shí)例強(qiáng)制向下轉(zhuǎn)換到某個(gè)協(xié)議類型,如果強(qiáng)轉(zhuǎn)失敗,會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤。
//定義協(xié)議:定義一個(gè)Double類型的可讀屬性area
protocol HasArea {
var area: Double { get }
}
//Circle類和Country類遵循協(xié)議HasArea
class Circle: HasArea {
let pi = 3.1415927
var radius: Double
var area: Double { return pi * radius * radius } //計(jì)算型
init(radius: Double) { self.radius = radius }
}
class Country: HasArea {
var area: Double //存儲(chǔ)型
init(area: Double) { self.area = area }
}
//Animal未遵循協(xié)議
class Animal {
var legs: Int
init(legs: Int) { self.legs = legs }
}
let objects: [AnyObject] = [
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
//迭代測(cè)試,并檢測(cè)協(xié)議
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”
十二、協(xié)議的可選性
協(xié)議可以定義可選要求,即遵循協(xié)議的類型可以選擇是否實(shí)現(xiàn)這些要求。
1.在協(xié)議中使用optional關(guān)鍵字作為前綴來(lái)定義可選要求。
2.可選要求用在你需要和Objective-C打交道的代碼中。協(xié)議和可選要求都必須帶上@objc屬性。
3.標(biāo)記@objc特性的協(xié)議只能被繼承自O(shè)bjective-C類的類或者@objc類遵循,其他類以及結(jié)構(gòu)體和枚舉均不能遵循這種協(xié)議。
4.協(xié)議中的可選要求可通過(guò)可選鏈?zhǔn)秸{(diào)用來(lái)使用,因?yàn)樽裱瓍f(xié)議的類型可能沒有實(shí)現(xiàn)這些可選要求
下面的例子定義了一個(gè)名為Counter的用于整數(shù)計(jì)數(shù)的類,它使用外部的數(shù)據(jù)源來(lái)提供每次的增量。數(shù)據(jù)源由CounterDataSource 協(xié)議定義,包含兩個(gè)可選要求:
//1.協(xié)議CounterDataSource包含兩個(gè)可選要求
@objc protocol CounterDataSource {
@objc optional func incrementForCount(count: Int) -> Int
@objc optional var fixedIncrement: Int { get }
}
//2.Counter類含有CounterDataSource?類型的可選屬性dataSource
class Counter {
var count = 0
var dataSource: CounterDataSource?
func increment() {
if let amount = dataSource?.incrementForCount?(count: count) {
count += amount
} else if let amount = dataSource?.fixedIncrement {
//通過(guò)可選鏈調(diào)用,每次使用的是fixedIncrement的3
count += amount
}
}
}
//3.ThreeSource類遵循了CounterDataSource協(xié)議
//它實(shí)現(xiàn)了可選屬性fixedIncrement,而并未實(shí)現(xiàn)incrementForCount方法
class ThreeSource: NSObject, CounterDataSource {
let fixedIncrement = 3
}
//4.使用ThreeSource實(shí)例作為Counter實(shí)例的數(shù)據(jù)源對(duì)象
var counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
counter.increment()
print(counter.count)
}
//測(cè)試結(jié)果:
//3
//6
//9
//12
注意:嚴(yán)格來(lái)講,CounterDataSource 協(xié)議中的方法和屬性都是可選的,因此遵循協(xié)議的類可以不實(shí)現(xiàn)這些要求,盡管技術(shù)上允許這樣做,不過(guò)最好不要這樣寫。
十三、協(xié)議擴(kuò)展
協(xié)議可以通過(guò)擴(kuò)展來(lái)為遵循協(xié)議的類型提供屬性、方法以及下標(biāo)的實(shí)現(xiàn)。通過(guò)這種方式,你可以基于協(xié)議本身來(lái)實(shí)現(xiàn)這些功能,而無(wú)需在每個(gè)遵循協(xié)議的類型中都重復(fù)同樣的實(shí)現(xiàn),也無(wú)需使用全局函數(shù)。
下面的代碼演示了協(xié)議擴(kuò)展的用法:
//協(xié)議:定義random函數(shù)生成隨機(jī)數(shù)方法
protocol RandomNumProtocol {
func random() -> Double
}
//擴(kuò)展RandomNumProtocol協(xié)議,增加了randomBool方法
//注意:通過(guò)協(xié)議擴(kuò)展,所有遵循協(xié)議的類型都能自動(dòng)獲得這個(gè)擴(kuò)展所增加的方法實(shí)現(xiàn),無(wú)需任何額外修改
extension RandomNumProtocol {
func randomBool() -> Bool {
return random() > 0.5
}
}
//遵循協(xié)議的類:一個(gè)實(shí)現(xiàn)了RandomNumProtocol協(xié)議的類
//RandomNum類只實(shí)現(xiàn)了協(xié)議方法random(),但是同樣可以使用協(xié)議擴(kuò)展里的方法randomBool()
class RandomNum: RandomNumProtocol {
var lastRandom = 42.0
let m = 139968.0
let a = 3877.0
let c = 29573.0
func random() -> Double {
//使用truncatingRemainder方法進(jìn)行浮點(diǎn)數(shù)取余
lastRandom = (lastRandom * a + c).truncatingRemainder(dividingBy: m)
return lastRandom / m
}
}
//測(cè)試代碼:
let generator = RandomNum()
print("Here's a random number: \(generator.random())")
//打印:Here's a random number: 0.37464991998171
print("And here's a random Boolean: \(generator.randomBool())")
//打?。篈nd here's a random Boolean: true
13.1.提供默認(rèn)實(shí)現(xiàn)
可以通過(guò)協(xié)議擴(kuò)展來(lái)為協(xié)議要求的屬性、方法以及下標(biāo)提供默認(rèn)的實(shí)現(xiàn)。但是,如果遵循協(xié)議的類型也為這些要求提供了自己的實(shí)現(xiàn),那么這些自定義實(shí)現(xiàn)將會(huì)替代擴(kuò)展中的默認(rèn)實(shí)現(xiàn)被使用。
注意:通過(guò)協(xié)議擴(kuò)展為協(xié)議要求提供的默認(rèn)實(shí)現(xiàn),這和可選的協(xié)議要求不同;雖然在這兩種情況下,遵循協(xié)議的類型都無(wú)需自己實(shí)現(xiàn)這些要求,但是通過(guò)擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)可以直接調(diào)用,而無(wú)需使用可選鏈?zhǔn)秸{(diào)用。
//協(xié)議:一個(gè)寵物協(xié)議,定義發(fā)出聲音的方法makeSound
protocol PetProtocol{
func makeSound()
}
//擴(kuò)展協(xié)議:提供默認(rèn)方法實(shí)現(xiàn)
extension PetProtocol{
func makeSound(){
print("aaaaaa。。。。")
}
}
class Cat:PetProtocol{
//因?yàn)橛袇f(xié)議擴(kuò)展,已經(jīng)提供了默認(rèn)的方法實(shí)現(xiàn);所以這里只遵循了協(xié)議
}
class Dog:PetProtocol{
func makeSound() {
print("汪汪汪。。。。")
}
}
//測(cè)試代碼:
let cat = Cat();
cat.makeSound(); //打印:aaaaaa。。。。
let dog = Dog();
dog.makeSound() //打?。和敉敉簟?。。。
13.2.為協(xié)議擴(kuò)展添加限制條件
在擴(kuò)展協(xié)議的時(shí)候,可以指定一些限制條件,只有遵循協(xié)議的類型滿足這些限制條件時(shí),才能獲得協(xié)議擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)。這些限制條件寫在協(xié)議名之后,使用 where子句來(lái)描述
例如:你可以擴(kuò)展Collection協(xié)議,通過(guò)限制集合元素遵循Equatable 協(xié)議, 作為標(biāo)準(zhǔn)庫(kù)的一部分,你可以使用==和!=操作符來(lái)檢查兩個(gè)元素的等價(jià)性和非等價(jià)性。
extension Collection where Element: Equatable {
func allEqual() -> Bool {
for element in self {
if element != self.first {
return false
}
}
return true
}
}
//如果集合中的所有元素都一致,allEqual()方法才返回 true
let equalNumbers = [100, 100, 100, 100, 100]
print(equalNumbers.allEqual()) //打印 "true"
let differentNumbers = [100, 100, 200, 100, 200]
print(differentNumbers.allEqual()) //打印 "false"
注意:如果多個(gè)協(xié)議擴(kuò)展都為同一個(gè)協(xié)議要求提供了默認(rèn)實(shí)現(xiàn),而遵循協(xié)議的類型又同時(shí)滿足這些協(xié)議擴(kuò)展的限制條件,那么將會(huì)使用限制條件最多的那個(gè)協(xié)議擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)。