swift - protocol 协议
Swift 中的协议(Protocol)是一种定义了方法、属性和其他要求的蓝图。类、结构体和枚举可以遵循(Adopt)协议来提供这些要求的实现。通过协议,可以实现多种设计模式,例如面向接口编程和多重继承。以下是关于 Swift 协议的概述。
定义协议
在 Swift 中,协议使用 protocol
关键字定义。协议可以规定类型必须实现的方法、属性和操作符。
以下是一个简单的协议示例:
protocol Animal {
var name: String { get }
var sound: String { get }
func makeSound()
}
这个例子中,Animal
协议定义了两个只读属性 name
和 sound
,以及一个方法 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
协议的类型必须实现 Movable
和 Rotatable
协议中的所有方法。
协议作为类型
协议可以作为函数参数、返回值、属性类型以及数组或字典类型的组成元素。当协议被用作类型时,它表示所有遵循这个协议的类型。
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!"
在这个示例中,我们定义了两个协议:Named
和 Aged
。然后,我们创建了一个遵循这两个协议的 Person
结构体。通过协议组合,我们可以定义一个接受同时遵循 Named
和 Aged
协议的类型参数的 wishHappyBirthday
函数。这种方法实现了多重继承,使得代码更加灵活。
协议底层实现
要了解协议底层实现原理,我们需要深入探讨 Swift 是如何在运行时处理协议的。
在 Swift 中,协议通常借助一个叫做虚表(Protocol Witness Table,PWT)的结构来实现。Swift 的编译器为每个遵循协议的类型生成相应的虚表,在运行时通过表中的指针查找实现。下面我们分步详细介绍这一过程:
-
协议定义:当你定义一个协议,编译器会为协议创建一个虚表原型,这个原型包含方法和属性的签名。虚表原型不包含任何具体的实现,只是一种规范。
-
遵循协议:当类型(如类、结构体)遵循协议时,编译器会自动生成一个虚表。这个虚表基于虚表原型,但包含类型对协议方法和属性的具体实现。在运行时,Swift 使用表中的指针查找实现。
-
扩展协议:若为协议提供扩展以实现默认方法,则在没有提供自定义实现的情况下,虚表中的指针会指向这些默认实现。
-
动态调用:在运行时,程序需要调用遵循协议的实例相关的方法。此时,Swift 会根据实例的类型查找对应的虚表,然后通过虚表中方法的指针找到并执行正确的实现。
这个底层实现模型使得协议遵循者能方便地使用动态派发调用相应的方法,保证了运行时性能与安全。此外,这种模式也使得类型遵循协议时具有很高的灵活性,因为类型可以在运行时决定实现协议的具体方法和属性。
虽然这里我们讨论的是 Swift 协议遵循者的虚表实现原理,但我们也可以将这个模型应用于其他 Swift 内部结构,例如:类继承。值得注意的是, Swift 的协议底层实现可能会在未来版本中继续改进和优化。