swift - protocol 協(xié)議

Swift 中的協(xié)議(Protocol)是一種定義了方法、屬性和其他要求的藍(lán)圖。類、結(jié)構(gòu)體和枚舉可以遵循(Adopt)協(xié)議來提供這些要求的實(shí)現(xiàn)。通過協(xié)議,可以實(shí)現(xiàn)多種設(shè)計(jì)模式,例如面向接口編程和多重繼承。以下是關(guān)于 Swift 協(xié)議的概述。

定義協(xié)議

在 Swift 中,協(xié)議使用 protocol 關(guān)鍵字定義。協(xié)議可以規(guī)定類型必須實(shí)現(xiàn)的方法、屬性和操作符。

以下是一個(gè)簡(jiǎn)單的協(xié)議示例:

protocol Animal {
    var name: String { get }
    var sound: String { get }
    func makeSound()
}

這個(gè)例子中,Animal 協(xié)議定義了兩個(gè)只讀屬性 namesound,以及一個(gè)方法 makeSound。

遵循協(xié)議

類、結(jié)構(gòu)體和枚舉可以遵循協(xié)議以提供協(xié)議要求的實(shí)現(xiàn)。使用繼承實(shí)現(xiàn)多個(gè)協(xié)議時(shí),協(xié)議名稱用逗號(hào)分隔。遵循協(xié)議后,類型需要實(shí)現(xiàn)協(xié)議的所有要求。

struct Dog: Animal {
    var name: String
    var sound: String
    
    func makeSound() {
        print("\(name) makes sound: \(sound)")
    }
}

let dog = Dog(name: "Buddy", sound: "Woof")
dog.makeSound() // 輸出: "Buddy makes sound: Woof"

在這個(gè)示例中,結(jié)構(gòu)體 Dog 遵循了 Animal 協(xié)議,并實(shí)現(xiàn)了協(xié)議定義的屬性和方法。

可選的實(shí)現(xiàn)

在某些情況下,協(xié)議可能需要定義可選的實(shí)現(xiàn)。這可以通過將協(xié)議與 @objc 屬性標(biāo)記并使用 optional 關(guān)鍵字實(shí)現(xiàn)。這種方式一般用于與 Objective-C 交互。

@objc protocol TestProtocol {
    func requiredMethod()
    @objc optional func optionalMethod()
}

這種情況下,遵循 TestProtocol 的類型只需要實(shí)現(xiàn) requiredMethod,而 optionalMethod 的實(shí)現(xiàn)是可選的。

協(xié)議屬性

協(xié)議可以定義實(shí)例屬性和類型屬性。協(xié)議屬性可以指定讀寫({ get set })或只讀({ get })要求。

protocol Vehicle {
    var brand: String { get }
    var numberOfWheels: Int { get set }
}

在這個(gè)示例中,Vehicle 協(xié)議定義了一個(gè)只讀屬性 brand 和一個(gè)可讀寫的屬性 numberOfWheels。

協(xié)議方法

協(xié)議可以定義實(shí)例方法和類型方法。定義協(xié)議方法時(shí),不需要提供方法內(nèi)部的實(shí)現(xiàn)。

protocol Playable {
    func playSound()
    static func description() -> String
}

在這個(gè)示例中,Playable 協(xié)議定義了一個(gè) playSound 實(shí)例方法和一個(gè) description 類型方法。遵循該協(xié)議的類、結(jié)構(gòu)體或枚舉需要實(shí)現(xiàn)這兩個(gè)方法。

mutating 方法

通常,“值類型”(例如結(jié)構(gòu)體和枚舉)在方法內(nèi)不能修改實(shí)例屬性。但是,在結(jié)構(gòu)體或枚舉內(nèi)的方法前加上 mutating 關(guān)鍵字,把這個(gè)方法標(biāo)記為可變方法(mutating method),可以讓方法修改實(shí)例屬性。

如果要讓協(xié)議方法修改實(shí)現(xiàn)類型的實(shí)例,可以使用 mutating 關(guān)鍵字標(biāo)記該方法。

在協(xié)議中使用 mutating 關(guān)鍵字的原因是讓協(xié)議方法具有靈活性。具體來說,在遵循協(xié)議的類型實(shí)現(xiàn)協(xié)議方法時(shí),在類中,即使方法沒有被標(biāo)記為 mutating,也可以修改實(shí)例屬性(因?yàn)轭愂且妙愋停?;而在結(jié)構(gòu)體和枚舉中,如果要修改實(shí)例屬性,則必須將方法標(biāo)記為 mutating。

我們通過一個(gè)示例來詳細(xì)了解 mutating 關(guān)鍵字在協(xié)議中的使用。下面是一個(gè)定義了一個(gè)名為 Switchable 的協(xié)議:

protocol Switchable {
    var isOn: Bool { get set }
    mutating func toggle()
}

這個(gè)協(xié)議要求遵循它的類型具有一個(gè) isOn 可讀寫屬性以及一個(gè) toggle 方法。我們?cè)?toggle 方法前加上了 mutating 關(guān)鍵字,允許方法修改遵循協(xié)議的類型的實(shí)例。

接下來,我們創(chuàng)建一個(gè)遵循 Switchable 協(xié)議的 LightSwitch 結(jié)構(gòu)體:

struct LightSwitch: Switchable {
    var isOn: Bool = false

    mutating func toggle() {
        isOn.toggle()
    }
}

在這個(gè)示例中,我們的 LightSwitch 結(jié)構(gòu)體遵循了 Switchable 協(xié)議,并實(shí)現(xiàn)了 isOn 和標(biāo)記為 mutating 的 toggle 方法。由于 toggle 方法被標(biāo)記為 mutating,它能夠修改結(jié)構(gòu)體內(nèi)的 isOn 屬性。

當(dāng)我們?cè)诖a中創(chuàng)建一個(gè) LightSwitch 實(shí)例并調(diào)用其 toggle 方法時(shí),實(shí)例的 isOn 屬性會(huì)發(fā)生改變:

var lightSwitch = LightSwitch()
print(lightSwitch.isOn) // 輸出:false

lightSwitch.toggle()
print(lightSwitch.isOn) // 輸出:true

初始化器要求

協(xié)議可以規(guī)定類型必須實(shí)現(xiàn)的指定初始化器。通過在協(xié)議中定義初始化器來實(shí)現(xiàn)這個(gè)要求。

protocol VehicleProtocol {
    var brand: String { get }
    
    init(brand: String)
}

在這個(gè)示例中,VehicleProtocol 協(xié)議定義了一個(gè)名為 brand 的只讀屬性和一個(gè)指定初始化器。遵循協(xié)議的類型需要實(shí)現(xiàn)這個(gè)初始化器以滿足協(xié)議要求。

類類型專屬的協(xié)議

你可以通過使用 AnyObject 關(guān)鍵字限制協(xié)議只能被類類型遵循,不能被結(jié)構(gòu)體和枚舉遵循。

protocol ClassOnlyProtocol: AnyObject {
    func classOnlyMethod()
}

在這個(gè)示例中,ClassOnlyProtocol 只能被類類型遵循,這意味著結(jié)構(gòu)體和枚舉不能遵循這個(gè)協(xié)議。

協(xié)議繼承

Swift 中的協(xié)議可以繼承一個(gè)或多個(gè)其他協(xié)議。這允許創(chuàng)建更特定的要求來擴(kuò)展現(xiàn)有協(xié)議定義。

protocol Movable {
    func move()
}

protocol Rotatable: Movable {
    func rotate()
}

在這個(gè)示例中,Rotatable 協(xié)議繼承了 Movable 協(xié)議,這意味著遵循 Rotatable 協(xié)議的類型必須實(shí)現(xiàn) MovableRotatable 協(xié)議中的所有方法。

協(xié)議作為類型

協(xié)議可以作為函數(shù)參數(shù)、返回值、屬性類型以及數(shù)組或字典類型的組成元素。當(dāng)協(xié)議被用作類型時(shí),它表示所有遵循這個(gè)協(xié)議的類型。

protocol Printable {
    func printDescription()
}

func printItems(_ items: [Printable]) {
    for item in items {
        item.printDescription()
    }
}

struct Book: Printable {
    var title: String
    
    func printDescription() {
        print("Book title: \(title)")
    }
}

struct Movie: Printable {
    var title: String
    
    func printDescription() {
        print("Movie title: \(title)")
    }
}

let items: [Printable] = [Book(title: "The Catcher in the Rye"), Movie(title: "The Shawshank Redemption")]
printItems(items)

在這個(gè)示例中,我們定義了一個(gè)名為 Printable 的協(xié)議,它用于定義可以描述自己的類型,并將其用作函數(shù)參數(shù)。這使我們可以創(chuàng)建一個(gè)通用的 printItems 函數(shù),它接收一個(gè) Printable 類型的數(shù)組并打印每個(gè)元素的信息。

在 Swift 中,協(xié)議(Protocol)是一種定義了方法、屬性和其他功能要求的藍(lán)圖。類、結(jié)構(gòu)體和枚舉可以遵循(Adopt)協(xié)議來提供這些要求的實(shí)現(xiàn)。通過這種方式,Swift 可以實(shí)現(xiàn)多種設(shè)計(jì)模式,例如面向接口編程和多重繼承。

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

協(xié)議擴(kuò)展是一種在 Swift 中為協(xié)議提供默認(rèn)實(shí)現(xiàn)的方法。通過協(xié)議擴(kuò)展,可以快速為遵循一個(gè)或多個(gè)協(xié)議的類型提供默認(rèn)實(shí)現(xiàn)。這減少了代碼重復(fù),同時(shí)讓功能可以更方便地復(fù)用。

以下示例演示了協(xié)議擴(kuò)展的用法:

protocol Greeting {
    func greet() -> String
}

// 為 Greeting 協(xié)議提供了一個(gè)默認(rèn)實(shí)現(xiàn)
extension Greeting {
    func greet() -> String {
        return "Hello, World!"
    }
}

// 遵循 Greeting 協(xié)議
struct Person: Greeting {}

let person = Person()
print(person.greet()) // 輸出: "Hello, World!"

在這個(gè)示例中,Greeting 協(xié)議中定義了一個(gè) greet 方法,而協(xié)議擴(kuò)展為 Greeting 提供了默認(rèn)實(shí)現(xiàn)。當(dāng) Person 結(jié)構(gòu)體遵循 Greeting 協(xié)議時(shí),它不需要手動(dòng)實(shí)現(xiàn) greet 方法,而是可以直接使用協(xié)議擴(kuò)展提供的默認(rèn)實(shí)現(xiàn)。

協(xié)議組合(Protocol Composition)

協(xié)議組合是一種將多個(gè)協(xié)議組合到一個(gè)要求中的方法。這允許類型可以同時(shí)遵循多個(gè)協(xié)議。在 Swift 中,通過協(xié)議組合來實(shí)現(xiàn)多重繼承。

以下示例演示了協(xié)議組合的用法:

protocol Named {
    var name: String { get }
}

protocol Aged {
    var age: Int { get }
}

struct Person: Named, Aged {
    var name: String
    var age: Int
}

func wishHappyBirthday(to celebrator: Named & Aged) {
    print("Happy birthday, \(celebrator.name), you're \(celebrator.age)!")
}

let person = Person(name: "John Doe", age: 30)
wishHappyBirthday(to: person) // 輸出: "Happy birthday, John Doe, you're 30!"

在這個(gè)示例中,我們定義了兩個(gè)協(xié)議:NamedAged。然后,我們創(chuàng)建了一個(gè)遵循這兩個(gè)協(xié)議的 Person 結(jié)構(gòu)體。通過協(xié)議組合,我們可以定義一個(gè)接受同時(shí)遵循 NamedAged 協(xié)議的類型參數(shù)的 wishHappyBirthday 函數(shù)。這種方法實(shí)現(xiàn)了多重繼承,使得代碼更加靈活。

協(xié)議底層實(shí)現(xiàn)

要了解協(xié)議底層實(shí)現(xiàn)原理,我們需要深入探討 Swift 是如何在運(yùn)行時(shí)處理協(xié)議的。

在 Swift 中,協(xié)議通常借助一個(gè)叫做虛表(Protocol Witness Table,PWT)的結(jié)構(gòu)來實(shí)現(xiàn)。Swift 的編譯器為每個(gè)遵循協(xié)議的類型生成相應(yīng)的虛表,在運(yùn)行時(shí)通過表中的指針查找實(shí)現(xiàn)。下面我們分步詳細(xì)介紹這一過程:

  1. 協(xié)議定義:當(dāng)你定義一個(gè)協(xié)議,編譯器會(huì)為協(xié)議創(chuàng)建一個(gè)虛表原型,這個(gè)原型包含方法和屬性的簽名。虛表原型不包含任何具體的實(shí)現(xiàn),只是一種規(guī)范。

  2. 遵循協(xié)議:當(dāng)類型(如類、結(jié)構(gòu)體)遵循協(xié)議時(shí),編譯器會(huì)自動(dòng)生成一個(gè)虛表。這個(gè)虛表基于虛表原型,但包含類型對(duì)協(xié)議方法和屬性的具體實(shí)現(xiàn)。在運(yùn)行時(shí),Swift 使用表中的指針查找實(shí)現(xiàn)。

  3. 擴(kuò)展協(xié)議:若為協(xié)議提供擴(kuò)展以實(shí)現(xiàn)默認(rèn)方法,則在沒有提供自定義實(shí)現(xiàn)的情況下,虛表中的指針會(huì)指向這些默認(rèn)實(shí)現(xiàn)。

  4. 動(dòng)態(tài)調(diào)用:在運(yùn)行時(shí),程序需要調(diào)用遵循協(xié)議的實(shí)例相關(guān)的方法。此時(shí),Swift 會(huì)根據(jù)實(shí)例的類型查找對(duì)應(yīng)的虛表,然后通過虛表中方法的指針找到并執(zhí)行正確的實(shí)現(xiàn)。

這個(gè)底層實(shí)現(xiàn)模型使得協(xié)議遵循者能方便地使用動(dòng)態(tài)派發(fā)調(diào)用相應(yīng)的方法,保證了運(yùn)行時(shí)性能與安全。此外,這種模式也使得類型遵循協(xié)議時(shí)具有很高的靈活性,因?yàn)轭愋涂梢栽谶\(yùn)行時(shí)決定實(shí)現(xiàn)協(xié)議的具體方法和屬性。

雖然這里我們討論的是 Swift 協(xié)議遵循者的虛表實(shí)現(xiàn)原理,但我們也可以將這個(gè)模型應(yīng)用于其他 Swift 內(nèi)部結(jié)構(gòu),例如:類繼承。值得注意的是, Swift 的協(xié)議底層實(shí)現(xiàn)可能會(huì)在未來版本中繼續(xù)改進(jìn)和優(yōu)化。

最后編輯于
?著作權(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)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 下面列舉的都是一些比較偏僻的,特有的,很多人不知道的協(xié)議特性。更多查看:https://segmentfault....
    清無閱讀 683評(píng)論 0 2
  • Swift — 協(xié)議(Protocol) [TOC] 協(xié)議定義了一個(gè)藍(lán)圖,規(guī)定了用來實(shí)現(xiàn)某一特定任務(wù)或者功能的方法...
    just東東閱讀 2,192評(píng)論 1 3
  • 1.協(xié)議的語法 定義協(xié)議: 遵守協(xié)議: 當(dāng)一個(gè)類既有父類,又遵守其他協(xié)議時(shí),將父類名寫在所遵守協(xié)議的前面: 2.屬...
    WSJay閱讀 27,433評(píng)論 3 18
  • 協(xié)議(Protocol) 協(xié)議可以用來定義 方法、屬性、下標(biāo) 的聲明,協(xié)議可以被 枚舉、結(jié)構(gòu)體、類 遵守(多個(gè)協(xié)議...
    iVikings閱讀 593評(píng)論 0 0
  • ??協(xié)議可以用來定義方法、屬性、下標(biāo)的生命,協(xié)議可以被枚舉、結(jié)構(gòu)體、類遵守(多個(gè)協(xié)議之間用逗號(hào),隔開) 協(xié)議中的方...
    Aliv丶Zz閱讀 1,940評(píng)論 0 2

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