swift - protocol 协议

Swift 中的协议(Protocol)是一种定义了方法、属性和其他要求的蓝图。类、结构体和枚举可以遵循(Adopt)协议来提供这些要求的实现。通过协议,可以实现多种设计模式,例如面向接口编程和多重继承。以下是关于 Swift 协议的概述。

定义协议

在 Swift 中,协议使用 protocol 关键字定义。协议可以规定类型必须实现的方法、属性和操作符。

以下是一个简单的协议示例:

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

这个例子中,Animal 协议定义了两个只读属性 namesound,以及一个方法 makeSound

遵循协议

类、结构体和枚举可以遵循协议以提供协议要求的实现。使用继承实现多个协议时,协议名称用逗号分隔。遵循协议后,类型需要实现协议的所有要求。

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"

在这个示例中,结构体 Dog 遵循了 Animal 协议,并实现了协议定义的属性和方法。

可选的实现

在某些情况下,协议可能需要定义可选的实现。这可以通过将协议与 @objc 属性标记并使用 optional 关键字实现。这种方式一般用于与 Objective-C 交互。

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

这种情况下,遵循 TestProtocol 的类型只需要实现 requiredMethod,而 optionalMethod 的实现是可选的。

协议属性

协议可以定义实例属性和类型属性。协议属性可以指定读写({ get set })或只读({ get })要求。

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

在这个示例中,Vehicle 协议定义了一个只读属性 brand 和一个可读写的属性 numberOfWheels

协议方法

协议可以定义实例方法和类型方法。定义协议方法时,不需要提供方法内部的实现。

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

在这个示例中,Playable 协议定义了一个 playSound 实例方法和一个 description 类型方法。遵循该协议的类、结构体或枚举需要实现这两个方法。

mutating 方法

通常,“值类型”(例如结构体和枚举)在方法内不能修改实例属性。但是,在结构体或枚举内的方法前加上 mutating 关键字,把这个方法标记为可变方法(mutating method),可以让方法修改实例属性。

如果要让协议方法修改实现类型的实例,可以使用 mutating 关键字标记该方法。

在协议中使用 mutating 关键字的原因是让协议方法具有灵活性。具体来说,在遵循协议的类型实现协议方法时,在类中,即使方法没有被标记为 mutating,也可以修改实例属性(因为类是引用类型);而在结构体和枚举中,如果要修改实例属性,则必须将方法标记为 mutating。

我们通过一个示例来详细了解 mutating 关键字在协议中的使用。下面是一个定义了一个名为 Switchable 的协议:

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

这个协议要求遵循它的类型具有一个 isOn 可读写属性以及一个 toggle 方法。我们在 toggle 方法前加上了 mutating 关键字,允许方法修改遵循协议的类型的实例。

接下来,我们创建一个遵循 Switchable 协议的 LightSwitch 结构体:

struct LightSwitch: Switchable {
    var isOn: Bool = false

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

在这个示例中,我们的 LightSwitch 结构体遵循了 Switchable 协议,并实现了 isOn 和标记为 mutating 的 toggle 方法。由于 toggle 方法被标记为 mutating,它能够修改结构体内的 isOn 属性。

当我们在代码中创建一个 LightSwitch 实例并调用其 toggle 方法时,实例的 isOn 属性会发生改变:

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

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

初始化器要求

协议可以规定类型必须实现的指定初始化器。通过在协议中定义初始化器来实现这个要求。

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

在这个示例中,VehicleProtocol 协议定义了一个名为 brand 的只读属性和一个指定初始化器。遵循协议的类型需要实现这个初始化器以满足协议要求。

类类型专属的协议

你可以通过使用 AnyObject 关键字限制协议只能被类类型遵循,不能被结构体和枚举遵循。

protocol ClassOnlyProtocol: AnyObject {
    func classOnlyMethod()
}

在这个示例中,ClassOnlyProtocol 只能被类类型遵循,这意味着结构体和枚举不能遵循这个协议。

协议继承

Swift 中的协议可以继承一个或多个其他协议。这允许创建更特定的要求来扩展现有协议定义。

protocol Movable {
    func move()
}

protocol Rotatable: Movable {
    func rotate()
}

在这个示例中,Rotatable 协议继承了 Movable 协议,这意味着遵循 Rotatable 协议的类型必须实现 MovableRotatable 协议中的所有方法。

协议作为类型

协议可以作为函数参数、返回值、属性类型以及数组或字典类型的组成元素。当协议被用作类型时,它表示所有遵循这个协议的类型。

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)

在这个示例中,我们定义了一个名为 Printable 的协议,它用于定义可以描述自己的类型,并将其用作函数参数。这使我们可以创建一个通用的 printItems 函数,它接收一个 Printable 类型的数组并打印每个元素的信息。

在 Swift 中,协议(Protocol)是一种定义了方法、属性和其他功能要求的蓝图。类、结构体和枚举可以遵循(Adopt)协议来提供这些要求的实现。通过这种方式,Swift 可以实现多种设计模式,例如面向接口编程和多重继承。

协议扩展(Protocol Extension)

协议扩展是一种在 Swift 中为协议提供默认实现的方法。通过协议扩展,可以快速为遵循一个或多个协议的类型提供默认实现。这减少了代码重复,同时让功能可以更方便地复用。

以下示例演示了协议扩展的用法:

protocol Greeting {
    func greet() -> String
}

// 为 Greeting 协议提供了一个默认实现
extension Greeting {
    func greet() -> String {
        return "Hello, World!"
    }
}

// 遵循 Greeting 协议
struct Person: Greeting {}

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

在这个示例中,Greeting 协议中定义了一个 greet 方法,而协议扩展为 Greeting 提供了默认实现。当 Person 结构体遵循 Greeting 协议时,它不需要手动实现 greet 方法,而是可以直接使用协议扩展提供的默认实现。

协议组合(Protocol Composition)

协议组合是一种将多个协议组合到一个要求中的方法。这允许类型可以同时遵循多个协议。在 Swift 中,通过协议组合来实现多重继承。

以下示例演示了协议组合的用法:

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!"

在这个示例中,我们定义了两个协议:NamedAged。然后,我们创建了一个遵循这两个协议的 Person 结构体。通过协议组合,我们可以定义一个接受同时遵循 NamedAged 协议的类型参数的 wishHappyBirthday 函数。这种方法实现了多重继承,使得代码更加灵活。

协议底层实现

要了解协议底层实现原理,我们需要深入探讨 Swift 是如何在运行时处理协议的。

在 Swift 中,协议通常借助一个叫做虚表(Protocol Witness Table,PWT)的结构来实现。Swift 的编译器为每个遵循协议的类型生成相应的虚表,在运行时通过表中的指针查找实现。下面我们分步详细介绍这一过程:

  1. 协议定义:当你定义一个协议,编译器会为协议创建一个虚表原型,这个原型包含方法和属性的签名。虚表原型不包含任何具体的实现,只是一种规范。

  2. 遵循协议:当类型(如类、结构体)遵循协议时,编译器会自动生成一个虚表。这个虚表基于虚表原型,但包含类型对协议方法和属性的具体实现。在运行时,Swift 使用表中的指针查找实现。

  3. 扩展协议:若为协议提供扩展以实现默认方法,则在没有提供自定义实现的情况下,虚表中的指针会指向这些默认实现。

  4. 动态调用:在运行时,程序需要调用遵循协议的实例相关的方法。此时,Swift 会根据实例的类型查找对应的虚表,然后通过虚表中方法的指针找到并执行正确的实现。

这个底层实现模型使得协议遵循者能方便地使用动态派发调用相应的方法,保证了运行时性能与安全。此外,这种模式也使得类型遵循协议时具有很高的灵活性,因为类型可以在运行时决定实现协议的具体方法和属性。

虽然这里我们讨论的是 Swift 协议遵循者的虚表实现原理,但我们也可以将这个模型应用于其他 Swift 内部结构,例如:类继承。值得注意的是, Swift 的协议底层实现可能会在未来版本中继续改进和优化。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容

  • 下面列举的都是一些比较偏僻的,特有的,很多人不知道的协议特性。更多查看:https://segmentfault....
    清無阅读 613评论 0 2
  • Swift — 协议(Protocol) [TOC] 协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法...
    just东东阅读 1,806评论 1 2
  • 1.协议的语法 定义协议: 遵守协议: 当一个类既有父类,又遵守其他协议时,将父类名写在所遵守协议的前面: 2.属...
    WSJay阅读 26,496评论 3 17
  • 协议(Protocol) 协议可以用来定义 方法、属性、下标 的声明,协议可以被 枚举、结构体、类 遵守(多个协议...
    iVikings阅读 561评论 0 0
  •   协议可以用来定义方法、属性、下标的生命,协议可以被枚举、结构体、类遵守(多个协议之间用逗号,隔开) 协议中的方...
    Aliv丶Zz阅读 1,761评论 0 2