Swift 5.1 (20) - 協(xié)議

協(xié)議定義了適合特定任務(wù)或功能的方法,屬性。協(xié)議可以由類(lèi),結(jié)構(gòu)或枚舉實(shí)現(xiàn),任何類(lèi)型實(shí)現(xiàn)協(xié)議的要求方法稱(chēng)為遵守協(xié)議。
個(gè)人理解:Swift中的協(xié)議所能實(shí)現(xiàn)的功能,不再局限于OC的代理委托。協(xié)議中定義的方法、屬性,在遵守協(xié)議的類(lèi)型的實(shí)例中可以直接調(diào)用和使用。協(xié)議這種新的能力,使得協(xié)議在Swift中的使用更加的靈活。

Protocol語(yǔ)法

為類(lèi),結(jié)構(gòu)體,枚舉定義協(xié)議的語(yǔ)法

protocol `protocolName` {
   //定義協(xié)議
}

自定義類(lèi)型遵守協(xié)議的語(yǔ)法
協(xié)議名稱(chēng)放在類(lèi)型名稱(chēng)之后,并用冒號(hào)分隔,多個(gè)協(xié)議時(shí),協(xié)議之間使用逗號(hào)。當(dāng)類(lèi)類(lèi)型有父類(lèi)時(shí)則父類(lèi)類(lèi)型名稱(chēng)放在協(xié)議之前并用逗號(hào)隔開(kāi)。

  • 值類(lèi)型
struct SomeStructure: FirstProtocol, AnotherProtocol {
    // 結(jié)構(gòu)體定義
}
  • 類(lèi)類(lèi)型
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
    // class定義
}

協(xié)議中定義屬性的要求

協(xié)議可以要求任何遵守此協(xié)議的類(lèi)型提供一個(gè)具有特定類(lèi)型和名稱(chēng)的實(shí)例屬性或類(lèi)屬性。但是協(xié)議不能指定屬性應(yīng)該是一個(gè)存儲(chǔ)屬性還是一個(gè)計(jì)算屬性。因此協(xié)議中定義的屬性只能要求屬性的名稱(chēng)與類(lèi)型,并且指定屬性是可get或支持getset的。

如果協(xié)議要求一個(gè)屬性是可getset的,那么協(xié)議對(duì)這個(gè)屬性的要求是不能被常量存儲(chǔ)屬性或者只讀的計(jì)算屬性滿(mǎn)足的。
如果協(xié)議僅要求一個(gè)屬性是可get的,那么這個(gè)要求可以被任何類(lèi)型的屬性滿(mǎn)足,同時(shí)如果有必要,這個(gè)屬性也是可set的。

屬性要求:

  • 協(xié)議中定義屬性必須始終聲明為變量屬性,并以var關(guān)鍵字為前綴。通過(guò)在類(lèi)型聲明后寫(xiě){ get set }來(lái)表示屬性可讀寫(xiě),通過(guò)寫(xiě){get}來(lái)表示可讀的屬性。
protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}
  • 協(xié)議中定義類(lèi)屬性時(shí),必須始終在前面使用static關(guān)鍵字。即使該協(xié)議的類(lèi)屬性在被類(lèi)實(shí)現(xiàn)時(shí),以classstatic為前綴都是符合協(xié)議的。
protocol protocolName {
   //定義協(xié)議
    static var something : String { get}
}
class ViewController: UIViewController,protocolName {
    class var something: String {//使用static也可
        "協(xié)議中定義的類(lèi)屬性"
    }
}

使用舉例:

定義協(xié)議如下

protocol Category {
    var kind : String {get set}
}

類(lèi)實(shí)現(xiàn)

class Animal : Category {
    var kind: String
    init(kinds:String) {
       kind = kinds
    }
    convenience init(){
        self.init(kinds:"??")
    }
}
let animal = Animal()
print(animal.kind)//??

注意: 協(xié)議中屬性為{get set}使用計(jì)算屬性時(shí)必須get{}set{}不能為get{};協(xié)議中屬性為{get}時(shí)則可以使用get{}或者get{}set{}

class Animal : Category {
    var houzhui : String = "類(lèi)"
    var kind: String {
        get{
          houzhui
        }
        set{
           houzhui = newValue + "類(lèi)"
        }
    }
}
//調(diào)用
let animal = Animal.init()
animal.kind = "??"
print(animal.kind) // ??類(lèi)

結(jié)構(gòu)體實(shí)現(xiàn)

struct AnimalStruct : Category {
    var kind: String    
}
let animal1 = AnimalStruct(kind:"??")
print(animal1.kind)//??

協(xié)議中定義方法的要求

協(xié)議中方法定義與普通實(shí)例和類(lèi)方法定義方式一樣,并且允許可變參數(shù),遵守與常規(guī)方法相同的規(guī)則 區(qū)別:沒(méi)有花括號(hào)和方法主體;無(wú)法為方法參數(shù)指定默認(rèn)值。會(huì)要求符合此協(xié)議的類(lèi)型實(shí)現(xiàn)協(xié)議中規(guī)定的實(shí)例方法和類(lèi)方法。

回顧可變參數(shù):形式:Type...,作用可以接受零個(gè)或多個(gè)指定類(lèi)型的值,在函數(shù)體內(nèi)可用作指定類(lèi)型的數(shù)組。

協(xié)議中定義類(lèi)方法時(shí),必須始終在前面使用static關(guān)鍵字。即使該協(xié)議的類(lèi)方法在被類(lèi)實(shí)現(xiàn)時(shí),以classstatic為前綴都是符合協(xié)議的。

定義方法協(xié)議

protocol MethodProtocol {
    func instanceMethod(para:String) -> String
    static func classMethod(para:String)->String
    func hasVariableParameter(somePara:String...) -> String
}

實(shí)現(xiàn)協(xié)議方法

class MethodProtocolClass : MethodProtocol {
    func hasVariableParameter(somePara: String...) -> String {
        var result : String = ""
        for item in somePara {
            result += item
        }
        return result
    }
    
    func instanceMethod(para: String) -> String {
        para + "實(shí)例方法協(xié)議實(shí)現(xiàn)"
    }
    class func classMethod(para: String) -> String {//!< static也可以
        para + "類(lèi)方法協(xié)議實(shí)現(xiàn)"
    }
}
//調(diào)用
let instance = MethodProtocolClass()
print(instance.instanceMethod(para: "hello!"))//!< hello!實(shí)例方法協(xié)議實(shí)現(xiàn)
print(MethodProtocolClass.classMethod(para: "Hi!"))//!< Hi!類(lèi)方法協(xié)議實(shí)現(xiàn)
print(instance.hasVariableParameter(somePara: "QiShare"," ","Come On","!"))//!< QiShare Come On!

可變方法要求

值類(lèi)型實(shí)現(xiàn)協(xié)議方法,修改值類(lèi)型實(shí)例本身,則此協(xié)議方法需要使用mutating關(guān)鍵字作為前綴。
若定義協(xié)議實(shí)例方法,旨在對(duì)所有遵守該協(xié)議的實(shí)例進(jìn)行修改,需要將此方法標(biāo)記mutating,以涵蓋值類(lèi)型。注:mutating標(biāo)記的協(xié)議方法,由類(lèi)類(lèi)型實(shí)現(xiàn)時(shí),無(wú)需編寫(xiě)mutating關(guān)鍵字,mutating關(guān)鍵字僅由結(jié)構(gòu)和枚舉使用。

protocol Togglable {
    mutating func switchOperate()
}

extension Bool : Togglable {
    mutating func switchOperate() {
        self = !self
    }  
}
//調(diào)用
var value = false
value.switchOperate()
print(value)//true

協(xié)議中定義初始化方法的要求

協(xié)議中定義初始化方法也和普通情況下定義初始化方法一樣,唯一區(qū)別便是沒(méi)有函數(shù)體和花括號(hào)。會(huì)要求遵守該協(xié)議的類(lèi)型實(shí)現(xiàn)此初始化方法。

protocol SomeProtocol {
    init(someParameter: Int)
}

類(lèi),實(shí)現(xiàn)協(xié)議中初始化方法的要求

遵守協(xié)議的類(lèi)類(lèi)型可以實(shí)現(xiàn)協(xié)議中的初始化方法為指定初始化方法或者便利初始化方法,無(wú)論哪種實(shí)現(xiàn),都必須使用required關(guān)鍵字標(biāo)記。

回顧:required關(guān)鍵字修飾初始化方法,標(biāo)記此初始化方法其所有子類(lèi)必須要實(shí)現(xiàn),子類(lèi)實(shí)現(xiàn)required標(biāo)記的初始化方法無(wú)需寫(xiě)override,但必須寫(xiě)required,指示該初始化方法的延續(xù)性。

//類(lèi),實(shí)現(xiàn)協(xié)議中初始化方法可謂是簽署了魔鬼契約,要求子子孫孫都要履行,可謂之曰霸氣側(cè)漏
class SomeClass: SomeProtocol {
   required init(someParameter: Int) {
   }
    //或者實(shí)現(xiàn)為便利初始化方法
   required convenience init(someParameter: Int) {
       self.init()
    }
}

使用required關(guān)鍵字確保了遵守此協(xié)議的類(lèi)類(lèi)型的所有子類(lèi)都能提供協(xié)議中初始化方法的實(shí)現(xiàn),使其子類(lèi)都遵守協(xié)議。
注意:使用final標(biāo)記類(lèi)類(lèi)型,實(shí)現(xiàn)協(xié)議初始化方法時(shí),無(wú)需使用required關(guān)鍵字,因?yàn)?code>final是阻止子類(lèi)化的。

特殊:若子類(lèi)重寫(xiě)了父類(lèi)的指定初始化方法,并且協(xié)議中的初始化方法與子類(lèi)重寫(xiě)的父類(lèi)的初始化方法一致,則子類(lèi)實(shí)現(xiàn)此協(xié)議時(shí),需同時(shí)使用requiredoverride修飾符進(jìn)行標(biāo)記。

protocol SomeProtocol {
    init()
}
class SomeSuperClass {
    init() {
    }
}
class SomeSubClass: SomeSuperClass, SomeProtocol {
    // "required"表示遵守協(xié)議; "override" 表示重寫(xiě)
    required override init() {
        //初始化方法零參數(shù)符合省略super.init()條件
    }
}

協(xié)議中定義可失敗的初始化方法

協(xié)議中定義的可失敗的初始化方法,可以被遵守該協(xié)議的類(lèi)型實(shí)現(xiàn)為可失敗的初始化方法或者不可失敗的初始化方法。

protocol SomeProtocol {
    init?(name:String)
}
class SomeClass : SomeProtocol {
    
    required init(name:String) {

    }
    required init?(name: String) {
        if name.isEmpty {return nil}
    }
    required init!(name: String) {
        
    }
}

作為類(lèi)型使用的協(xié)議

協(xié)議本身實(shí)際上并未實(shí)現(xiàn)任何功能。但是卻可以將協(xié)議用作完整類(lèi)型。
使用協(xié)議作為類(lèi)型有時(shí)也稱(chēng)為存在類(lèi)型,存在類(lèi)型來(lái)自短語(yǔ)“存在類(lèi)型T,使得T遵守協(xié)議”。類(lèi)似OC中的@property (nonatomic, weak) id<protocolName> delegate

可以在允許使用其他類(lèi)型的許多地方使用協(xié)議:

  • 作為函數(shù),方法,初始化方法的參數(shù)類(lèi)型或返回值類(lèi)型
  • 作為常量,變量,或?qū)傩缘念?lèi)型
  • 作為數(shù)組,字典或其他容器的元素類(lèi)型
protocol RandomNumberProtocol {
    func random() -> UInt
}
class RandomNumberSmallGenerator: RandomNumberProtocol {
    var name = "遵守協(xié)議的屬性"
    func random() -> UInt {
        return UInt(arc4random() % 10)
    }
}
class RandomNumberBigGenerator: RandomNumberProtocol {
    func random() -> UInt {
        return UInt(arc4random() % 10) + 10
    }
}
class BoomTest {
    var generator : RandomNumberProtocol //!< 協(xié)議作為類(lèi)型修飾屬性
    var description : String
    //!< 協(xié)議作為類(lèi)型修飾方法的參數(shù),所有符合`RandomNumberProtocol`的類(lèi)型都可以傳入
    init(des:String,random:RandomNumberProtocol) {
        generator = random
        print("初始化時(shí)調(diào)用一下協(xié)議方法:\(generator.random())")
        description = des
    }
    //!<如何使用協(xié)議中的方法呢?需要單獨(dú)定義方法
    func toRandom() -> String {
        description + "\(generator.random())"
    }  
}
//調(diào)用
let randomNum = BoomTest.init(des: "??", random: RandomNumberSmallGenerator())
for _ in 0...2 {
    let result = randomNum.toRandom()
    print(result)
}
//輸出
??5
??4
??0

協(xié)議類(lèi)型的集合:
協(xié)議作為集合的元素類(lèi)型舉例:

let protocolArray : [RandomNumberProtocol] = [RandomNumberSmallGenerator(),RandomNumberBigGenerator()]
for item in protocolArray {
    let num = item.random()
    print(num) //!< 1 17
}

參數(shù)或?qū)傩詾閰f(xié)議類(lèi)型時(shí),當(dāng)傳入遵守此協(xié)議的類(lèi)型時(shí),是否可以通過(guò)此屬性或參數(shù),來(lái)訪(fǎng)問(wèn)遵守此協(xié)議類(lèi)型的方法或者屬性?答案是不能直接訪(fǎng)問(wèn),通過(guò)協(xié)議類(lèi)型的屬性或參數(shù)只能訪(fǎng)問(wèn)到協(xié)議中的方法或?qū)傩?,如何做到?需要?lèi)型轉(zhuǎn)換。

for item in protocolArray {
    let num = item.random()
    if let smallGenerator = item as? RandomNumberSmallGenerator {
        print(smallGenerator.name) //log:遵守協(xié)議的屬性
    }
    print(num) //!< 1 17
}

Delegation

委托是一種設(shè)計(jì)模式,使類(lèi)或結(jié)構(gòu)體可以將其某些職責(zé)委托給其他類(lèi)型的實(shí)例。通過(guò)定義封裝委托職責(zé)的協(xié)議來(lái)實(shí)現(xiàn)此設(shè)計(jì)模式,從而保證遵守協(xié)議的類(lèi)型(或稱(chēng)委托)提供協(xié)議需要的的功能。委托可用于響應(yīng)特定操作,或從外部源檢索數(shù)據(jù),而無(wú)需了解該源的底層類(lèi)型。
這種模式OC中也是有的,示例如下:

protocol PersonActivity {
    func sleep()
    func eat()
    func play()
}
//通過(guò)將AnyObject協(xié)議添加到協(xié)議的繼承列表中,可以將遵守此協(xié)議的類(lèi)型限制為類(lèi)類(lèi)型(而不是結(jié)構(gòu)或枚舉)。
protocol PersonDelegate : AnyObject {
    func personNowDoSomething(name: String,SomeThing:String) -> Void
}

class Person: PersonActivity {

    var name : String
    var age : UInt
    //!< 使用了weak 因此遵守此協(xié)議的類(lèi)型必須是類(lèi)類(lèi)型,故`PersonDelegate`繼承`anyobjct`
    weak var delegate : PersonDelegate?
    
    init(name:String = "QiShare",age:UInt = 1) {
        self.name = name
        self.age = age
    }
    
    func activity() -> Void {
        let num = arc4random()%3
        switch num {
        case 0:
            sleep()
        case 1:
            eat()
        default:
            play()
        }
    }
    
    func sleep() {
        delegate?.personNowDoSomething(name:name,SomeThing: "睡覺(jué)")
    }
    
    func eat() {
        delegate?.personNowDoSomething(name:name,SomeThing: "吃飯")
    }
    
    func play() {
        delegate?.personNowDoSomething(name:name,SomeThing: "玩耍")
    }
}

class DelegationClass:PersonDelegate{
    func personNowDoSomething(name:String,SomeThing: String) {
        print(name + "正在" + SomeThing) //會(huì)輸出
    }
}

注意:為了防止強(qiáng)引用循環(huán),需要將委托聲明為弱引用,即使用weak修飾。

僅類(lèi)類(lèi)型遵守的協(xié)議Class-Only Protocols

通過(guò)將AnyObject協(xié)議添加到協(xié)議的繼承中,可以將遵守此協(xié)議的類(lèi)型限制為類(lèi)類(lèi)型(而不是結(jié)構(gòu)或枚舉,否則會(huì)觸發(fā)編譯時(shí)錯(cuò)誤)。使用weak 修飾的協(xié)議類(lèi)型的屬性,則傳入的遵守此協(xié)議的實(shí)例只能為類(lèi)類(lèi)型實(shí)例。

使用擴(kuò)展使某個(gè)類(lèi)型遵守協(xié)議

使用擴(kuò)展使得現(xiàn)有類(lèi)型遵守新的協(xié)議。擴(kuò)展能夠?yàn)楝F(xiàn)有類(lèi)型添加計(jì)算屬性,下標(biāo),方法,因此能夠添加協(xié)議要求的方法,屬性。
注:當(dāng)協(xié)議被遵守和實(shí)現(xiàn)在實(shí)例的類(lèi)型擴(kuò)展中,現(xiàn)有類(lèi)型的實(shí)例會(huì)自動(dòng)采用和遵守。

protocol Togglable {
    mutating func switchOperate()
}

extension Bool : Togglable {
    mutating func switchOperate() {
        self = !self
    }  
}
//調(diào)用
var value = false
value.switchOperate()
print(value)//true

有條件地遵守協(xié)議

泛型僅在特定的條件下能夠滿(mǎn)足協(xié)議的要求。通過(guò)協(xié)議名稱(chēng)后使用泛型的where子句來(lái)使泛型類(lèi)型有條件的遵守協(xié)議。

// Array就是泛型`Array<Element>`
extension Array : RandomNumberProtocol where Element : RandomNumberProtocol {
    func random() -> UInt {
        732
    }
}
//調(diào)用
let num1 = RandomNumberSmallGenerator()
let num2 = RandomNumberSmallGenerator()
let protocolArray = [num1,num2]
print("\(protocolArray.random())")//!< 732

上述測(cè)試示例總結(jié):

  • extension Array : RandomNumberProtocol where Element : RandomNumberProtocol表示Array中的元素都遵守某個(gè)協(xié)議時(shí),Array的實(shí)例才能使用此協(xié)議方法。
  • let num2 = RandomNumberBigGenerator()則會(huì)報(bào)錯(cuò)Protocol type 'Any' cannot conform to 'RandomNumberProtocol' because only concrete types can conform to protocols。故Array必須是固定類(lèi)型,不能是AnyAnyObject,才能遵守此協(xié)議。
  • let protocolArray : [RandomNumberProtocol] = [num1,num2]則會(huì)報(bào)錯(cuò):Protocol type 'RandomNumberProtocol' cannot conform to 'RandomNumberProtocol' because only concrete types can conform to protocols
extension Array : RandomNumberProtocol where Element : RandomNumberProtocol

不能使用特定類(lèi)型的Array,例如:Array<String>。

通過(guò)擴(kuò)展聲明遵守協(xié)議

如果類(lèi)型已經(jīng)遵守協(xié)議的所有要求,但尚未聲明該類(lèi)型遵守協(xié)議,則可以通過(guò)一個(gè)空擴(kuò)展 來(lái)聲明。

protocol RandomNumberProtocol {
    func random() -> UInt
}

class RandomNumberSmallGenerator {
    var name = "遵守協(xié)議的屬性"
    func random() -> UInt {
        return UInt(arc4random() % 10)
    }
}

extension RandomNumberSmallGenerator : RandomNumberProtocol {}

協(xié)議的繼承

協(xié)議可以繼承一個(gè)或多個(gè)其他協(xié)議,并且可以在繼承的要求(方法、屬性)之上添加其他要求。協(xié)議繼承的語(yǔ)法類(lèi)似于類(lèi)繼承的語(yǔ)法,多個(gè)繼承的協(xié)議之間逗號(hào)分隔?;菊Z(yǔ)法:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
}

示例如下:

protocol TextDescription {
    func TextDescription()->String
}
protocol AdditionTextDescription : TextDescription {
    var goodEvaluation : String {get}
}
struct Evaluation : AdditionTextDescription {
    var goodEvaluation: String {
         TextDescription() + "很好!"
    }
    func TextDescription() -> String {
        "這個(gè)結(jié)構(gòu)體"
    }
}
//調(diào)用
let evaluation = Evaluation.init()
print(evaluation.goodEvaluation)//!< 這個(gè)結(jié)構(gòu)體很好!

協(xié)議組合

協(xié)議組合可以組合多個(gè)協(xié)議成為一個(gè)臨時(shí)的本地協(xié)議,該協(xié)議具備了組合的所有協(xié)議要求,且不會(huì)任何新的協(xié)議類(lèi)型。這對(duì)于要求某個(gè)類(lèi)型同時(shí)遵守多個(gè)協(xié)議是很有用的。
協(xié)議組合的形式SomeProtocol & AnotherProtocol??梢允褂?code>&作為分隔符列出需要的任意數(shù)量的協(xié)議。另外,協(xié)議組合也可以包含類(lèi)類(lèi)型,包含的類(lèi)類(lèi)型,可以用來(lái)指定遵守組合協(xié)議的類(lèi)的父類(lèi),驗(yàn)證得知:本類(lèi)也是可以的。

protocol Color {
    var color : String {get}
}
protocol Feature {
    var feature : String {get}
}

class Dog {
   var name : String
    init(_ name : String = "阿里克") {
        self.name = name
    }
}

class Cat : Color,Feature{
    var name : String
    var color: String{
        "黑色"
    }
    var feature: String{
        "撒嬌"
    }
    init(_ name : String = "小黃") {
        self.name = name
    }
}
class Husky: Dog,Color,Feature {
    var color: String
    var feature: String
    init(_ name : String,_ color : String,_ feature : String) {
        self.color = color
        self.feature = feature
    }
}

class Kid {
    static func hasCat(pet:Cat&Color&Feature)->String {
        "恭喜你獲得了寵物貓:\(pet.name) 顏色:\(pet.color) 特點(diǎn):\(pet.feature)"
    }
    static func hasDog(pet:Dog&Color&Feature)->String {
        "恭喜你獲得了寵物狗:\(pet.name) 顏色:\(pet.color) 特點(diǎn):\(pet.feature)"
    }
   ///本類(lèi)也是可以的
   ///static func hasDog(pet: Husky&Color&Feature)->String {
   ///     "恭喜你獲得了寵物狗:\(pet.name) 顏色:\(pet.color) 特點(diǎn):\(pet.feature)"
   /// }
}
//調(diào)用
let cat = Kid.hasCat(pet: Cat.init())//!< 恭喜你獲得了寵物貓:小黃 顏色:黑色 特點(diǎn):撒嬌
print(cat)
let dog = Kid.hasDog(pet: Husky.init("哈慫奇", "火紅", "家中地雷"))//!< 恭喜你獲得了寵物狗:阿里克 顏色:火紅 特點(diǎn):家中地雷
print(dog)

檢查類(lèi)型實(shí)例是否遵守特定協(xié)議

使用isas檢查類(lèi)型實(shí)例是否遵守特定協(xié)議并且轉(zhuǎn)換為特定協(xié)議。語(yǔ)法與類(lèi)型轉(zhuǎn)換一樣。

  • 如果實(shí)例遵守協(xié)議,則is運(yùn)算符返回true,否則返回false
  • as?向下轉(zhuǎn)換為目標(biāo)協(xié)議類(lèi)型的可選值,如果實(shí)例不遵守該協(xié)議,則返回nil。
  • as!強(qiáng)制向下轉(zhuǎn)換為目標(biāo)協(xié)議類(lèi)型,轉(zhuǎn)換失敗則是觸發(fā)運(yùn)行時(shí)錯(cuò)誤。
protocol HasArea {
    var area: Double { get }
}
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 }
}
//調(diào)用
let objects: [AnyObject] = [
    Circle(radius: 2.0),
    Country(area: 243_610),
    Husky.init("哈士奇", "火紅", "家中地雷")
]
for object in objects {
    if let objectWithArea = object as? HasArea {
        print("Area is \(objectWithArea.area)")
    } else {
        print("Something that doesn't have an area")
    }
}

可選協(xié)議要求

我們可以定義協(xié)議可選的要求,這些要求不是必須被遵守此協(xié)議的類(lèi)型實(shí)現(xiàn)的。即:我們可以編寫(xiě)遵守某個(gè)協(xié)議的自定義類(lèi),而無(wú)需實(shí)現(xiàn)任何可選協(xié)議要求。
協(xié)議的可選要求的定義:使用optional修飾符作為前綴,定義協(xié)議要求即可。
在協(xié)議的可選要求中使用方法或?qū)傩詴r(shí),其類(lèi)型將自動(dòng)變?yōu)榭蛇x。例如,類(lèi)型(Int)->String的方法變?yōu)?code>((Int)->String)?。注意:是整個(gè)函數(shù)類(lèi)型都包裝在可選內(nèi)容中,而不是方法的返回值中。本質(zhì)是:協(xié)議中定義的方法名稱(chēng)便是函數(shù)的實(shí)例,此函數(shù)類(lèi)型變?yōu)榱丝蛇x。

protocol CounterDataSource {
    optional func increment(forCount count: Int) -> Int
    optional var fixedIncrement: Int { get }
}

實(shí)際操作過(guò)程中發(fā)現(xiàn)不使用@objc修飾協(xié)議,是無(wú)法使用optional修飾符的,否則報(bào)錯(cuò):'optional' requirements are an Objective-C compatibility feature
總結(jié):通過(guò)這個(gè)錯(cuò)誤我們才知道原來(lái),可選協(xié)議要求是Objective-C兼容性功能,optional必須與@objc聯(lián)合使用:

  • optional必須在@objc修飾的協(xié)議下使用,并且必須采用@objc optional的形式。
  • 協(xié)議的可選要求不能被結(jié)構(gòu)體或枚舉采用。

使用舉例:

@objc protocol CounterDataSource {
     @objc optional func increment(forCount count: Int) -> Int
     @objc optional var fixedIncrement: Int { get }
}
class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
       ///注意:因?yàn)閛ptional, 函數(shù)類(lèi)型實(shí)例increment自動(dòng)變?yōu)榭蛇x項(xiàng),屬性也是一樣的,
        if let amount = dataSource?.increment?(forCount: count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}
class ThreeSource: CounterDataSource {
    let fixedIncrement: Int = 3
}
//調(diào)用
let counter = Counter()
counter.dataSource = ThreeSource()
for _ in 1...4 {
    counter.increment()
    print(counter.count) // 3 6 9 12
}

協(xié)議擴(kuò)展

協(xié)議通過(guò)擴(kuò)展可以為遵守協(xié)議的類(lèi)型提供方法,初始化,下標(biāo)和計(jì)算屬性的實(shí)現(xiàn)。這一點(diǎn)允許我們?yōu)閰f(xié)議本身定義行為,而不是基于遵守協(xié)議的每個(gè)類(lèi)型。
協(xié)議擴(kuò)展可以將實(shí)現(xiàn)添加到遵守協(xié)議的類(lèi)型中,但不能使協(xié)議要求進(jìn)行擴(kuò)展或從另一個(gè)協(xié)議繼承。協(xié)議繼承始終在協(xié)議聲明本身中指定。

protocol Color {
    var color : String {get}
}
protocol Feature {
    var feature : String {get}
}
//擴(kuò)展協(xié)議Feature
extension Feature {
    var like_feature : String {
        return "喜歡" + feature
    }
}
class Kid {
    static func hasCat(pet:Cat&Color&Feature)->String {
        "恭喜你獲得了寵物貓,特點(diǎn):\(pet.like_feature)"
    }
    static func hasDog(pet:Dog&Color&Feature)->String {
        "恭喜你獲得了寵物狗,特點(diǎn):\(pet.like_feature)"
    }
}
//調(diào)用結(jié)果
恭喜你獲得了寵物貓,特點(diǎn):喜歡撒嬌
恭喜你獲得了寵物狗, 特點(diǎn):喜歡搗蛋

協(xié)議要求提供默認(rèn)實(shí)現(xiàn)

我們可以使用協(xié)議擴(kuò)展為當(dāng)前協(xié)議要求定義的任何方法或計(jì)算屬性提供默認(rèn)的實(shí)現(xiàn)。如果一個(gè)遵守此協(xié)議的類(lèi)型提供了屬于它自己關(guān)于某個(gè)協(xié)議要求的實(shí)現(xiàn),那么將會(huì)代替協(xié)議擴(kuò)展中提供的那一個(gè)。
通過(guò)擴(kuò)展提供協(xié)議的默認(rèn)實(shí)現(xiàn),也可以使得遵守該協(xié)議的類(lèi)型不必提供它們自己的實(shí)現(xiàn),這點(diǎn)和可選協(xié)議要求一樣。但是采用這種方式為可選協(xié)議要求增加默認(rèn)實(shí)現(xiàn)后,則無(wú)需使用可選鏈接。

protocol Color {
    var color : String {get}
}
protocol Feature {
    var feature : String {get}
}
//為這兩個(gè)協(xié)議提供默認(rèn)實(shí)現(xiàn)
extension Color {
    var color : String {
        "彩虹色"
    }
//    var feature : String {
//        "動(dòng)物能有啥子特點(diǎn)!"
//    }
}
extension Feature {
    var feature : String {
        "動(dòng)物能有啥子特點(diǎn)?"
    }
}
class Cat : Color,Feature{
    var name : String
    var color: String{
        "黑色"
    }
//    var feature: String{
//        "撒嬌"
//    }
    init(_ name : String = "小黃") {
        self.name = name
    }
}
class Kid {
    static func hasCat(pet:Cat&Color&Feature)->String {
        "恭喜你獲得了寵物貓:\(pet.name) 顏色:\(pet.color) 特點(diǎn):\(pet.like_feature)"
    }
}
//log:恭喜你獲得了寵物貓:小黃 顏色:黑色 特點(diǎn):喜歡動(dòng)物能有啥子特點(diǎn)?

添加約束到協(xié)議擴(kuò)展

定義協(xié)議擴(kuò)展時(shí),在協(xié)議擴(kuò)展中定義的方法與屬性可用之前,我們可以指定遵守此協(xié)議的類(lèi)型必須滿(mǎn)足的約束條件,否則將不可用。
方式:在要擴(kuò)展的協(xié)議名稱(chēng)使用泛型where子句添加約束。

extension Collection where Element: Equatable {
    func allEqual() -> Bool {
        for element in self {
            if element != self.first {
                return false
            }
        }
        return true
    }
}

let equalNumbers = [100, 100, 100, 100, 100]
let differentNumbers = [100, 100, 200, 100, 200]
//let differentNumbers : [Cat] = [Cat.init(), Cat.init()]
//觸發(fā)錯(cuò)誤:Referencing instance method 'allEqual()' on 'Collection' requires that 'Cat' conform to 'Equatable'
print(equalNumbers.allEqual())//true
print(differentNumbers.allEqual()) //false

參考資料:
swift 5.1官方編程指南

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容