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