一篇读懂Swift中的协议(Protocol)

406 阅读8分钟

- 协议定义了一组方法、属性、以及其他特定的任务需求。

- 通过协议,可以定义接口,而不必关心具体的实现。

- 协议可被类、结构体、或枚举类型遵循。

一、协议的语法

  • 协议定义
protocol SomeProtocol { }
  • 多个协议用逗号分开
struct SomeStruct: FirstProtocol, AnotherProtocol { }
  • 若一个类拥有父类,将父类名放在协议名之前
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol { }

二、属性要求

  1. 属性必须明确是 可读的{ get }或 可读的和可写的{ get set }。
  2. 属性要求定义为变量属性,在名称前面使用 var 关键字。
  3. 协议中定义类型属性时必须在前面添加 static 关键字。
protocol SomeProtocol {
    var mustBeSettable: Int { get set }
    var doesNotNeedToBeSettable: Int { get }
}

示例一: 定义一个具有只读属性的协议。

  1. 首先定义一个协议 FullyNamed,FullyNamed 定义了一个只读属性 fullName。
  2. Person 结构体遵循了 FullyNamed 协议。
protocol FullyNamed {
    var fullName: String { get }
}

struct Person: FullyNamed {
    var fullName: String
}

var john = Person(fullName: "John Appleseed")
john.fullName = "John Doe"     // 更改 fullName
print(john.fullName)           // 打印:John Doe

fullName不是只读属性吗?为什么不报错?

然而,fullName属性 只在协议 FullyNamed 中被定义为只读属性 { get };

在结构体 Person 中,它是一个实际的存储属性(即可以读写),具有默认的读写能力,因此你可以对它进行赋值。

示例二:定义一个带有类型属性的协议 static

protocol Animal {
    static var species: String { get }
    func weight() -> Double
}

struct Dog: Animal {
    static var species: String {
        return "犬科"
    }
    func weight() -> Double {
        return 10.0
    }
}

struct Cat: Animal {
    static var species: String {
        return "猫科"
    }
    func weight() -> Double {
        return 2.5
    }
}
// 使用类型属性
print("Dog species: \(Dog.species)")    // Dog species: 犬科
print("Cat species: \(Cat.species)")    // Cat species: 猫科

// 使用实例属性
let dog = Dog()
let cat = Cat()
print("dog weight: \(dog.weight())kg")  // dog weight: 10.0kg
print("cat weight: \(cat.weight())kg")  // cat weight: 2.5kg

三、方法要求

  1. 协议可以实现实例方法和类方法;
  2. 方法参数不能定义默认值;
  3. 定义类方法时,同样需要在其之前添加 static 关键字;
protocol SomeProtocol {
   // 实例方法
   func someMethod()
}
protocol SomeProtocol {
   // 类方法
   static func someTypeMethod()
}
异变方法
  1. 在方法的 func 关键字之前使用 mutating 关键字,来表示在该方法可以改变实例的属性,这样的方法被称为异变方法
  2. mutating 方法允许你在方法中修改结构体或枚举的属性。
  3. mutating 并不适用于属性本身的定义,它用于方法中。

示例一:结构体

protocol FullyNamed {
    var fullName: String { get set }
}

struct Person: FullyNamed {
    var fullName: String
    
    mutating func updateFullName(to newName: String) {
        self.fullName = newName
    }
}
var john = Person(fullName: "John Appleseed")
print(john.fullName)        // 打印:John Appleseed

john.updateFullName(to: "John Doe")
print(john.fullName)        // 打印:John Doe

示例二:枚举

protocol Togglable {
    mutating func toggle()
}

enum OnOffSwitch: Togglable {
    case off, on
    mutating func toggle() {
        switch self {
        case .off:
            self = .on
        case .on:
            self = .off
        }
    }
}
var lightSwitch = OnOffSwitch.off       // .off
lightSwitch.toggle()                    // .on

四、初始化器要求

协议可以要求遵循协议的类型 实现指定的初始化器;

protocol SomeProtocol {
    init(someParameter: Int)
}

当一个协议要求类实现一个初始化器时,必须使用 required 关键字标记这个初始化器,这样它才能被子类继承。

示例一:使用指定初始化器满足协议要求

protocol Animal {
    init(species: String)
}

class Dog: Animal {
    var species: String
    
    required init(species: String) {
        self.species = species
    }
}

class GuideDog: Dog {
    var name: String
    init(species: String, name: String) {
        self.name = name
        super.init(species: species)
    }
    
    required init(species: String) {
        self.name = "没起名"
        super.init(species: species)
    }
}
let dog = GuideDog(species: "导盲犬", name: "大黄")
print(dog.species)    // 导盲犬
print(dog.name)       // 大黄

示例二:使用便捷初始化器实现协议要求

protocol Shape {
    init(name: String)
}

class Rectangle: Shape {
    
    var name: String
    
    required init(name: String) {
        self.name = name
    }
    
    convenience init() {
        self.init(name: "默认形状")
    }
}
let shape = Rectangle()
print(shape.name)      // 默认形状

let rectangle = Rectangle(name: "矩形")
print(rectangle.name)  // 矩形

如果一个子类重写了父类指定的初始化器,并且遵循协议实现了初始化器要求,那么就要为这个初始化器的实现添加 requiredoverride 两个修饰符:

protocol SomeProtocol {
    init()
}
 
class SomeSuperClass {
    init() {
        
    }
}
 
class SomeSubClass: SomeSuperClass, SomeProtocol {
    required override init() {
        
    }
}

五、将协议作为类型

协议自身并不实现功能; 由于它是一个类型,你可以在很多其他类型可以使用的地方使用协议;

  1. 在函数、方法或初始化器里作为形式参数类型 或 返回类型;
  2. 作为常量、变量或者属性的类型;
  3. 作为数组、字典等存储类型;

示例:

// RandomNumberGenerator 协议要求采用该协议的类型都必须有一个实例方法 random,而且返回一个 Double 的值;
protocol RandomNumberGenerator {
    func random() -> Double
}

// 遵循 RandomNumberGenerator 协议的类的实现
// linear congruential generator 著名的伪随机数算法
class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m))
        return lastRandom / m
    }
}

// 这个例子定义了一个 Dice 的新类,他表示一个n面骰子
// sides 表示有多少个面
// generator 提供了随机数的生成器来生成骰子的值
class Dice {
    let sides: Int
    let generator: RandomNumberGenerator  // 属性,RandomNumberGenerator类型
    
    // 形参,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
 */

六、协议委托

协议委托(或代理模式)是一种常用的设计模式,在Swift中用于实现对象之间的松耦合和事件通知。

通过使用委托,类可以将某些任务或事件委托给其他类,从而提高代码的模块化和可维护性;

示例:假设我们有一个下载任务类 Downloader,它负责下载数据。 当下载完成,我们希望通知某个对象,进行相应的处理。

protocol DownloaderDelegate: AnyObject {
    func downloadDidFinish()
    func downloadDidFail()
}

class Downloader {
    weak var delegate: DownloaderDelegate?
    
    func startDownload() {
        // 模拟一个下载过程
        let success = Bool.random()
        if success {
            // 下载成功
            delegate?.downloadDidFinish()
        } else {
            // 下载失败
            delegate?.downloadDidFail()
        }
    }
}

class ViewController: DownloaderDelegate {
    func downloadDidFinish() {
        print("下载成功")
    }
    
    func downloadDidFail() {
        print("下载失败")
    }
    
    func startDownloadTask() {
        let downloader = Downloader()
        downloader.delegate = self
        downloader.startDownload()
    }
}

通过协议委托模式,可以将任务和事件处理分离,使代码更加模块化和易于维护。


七、协议的继承

协议可以继承一个或多个其他协议,从而扩展和组合它们的功能;

类似于类的继承,但协议可以多重继承,类只能单继承;

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
    // protocol definition goes here
}

示例

protocol Animal {
    var species: String { get }
    func makeSound() -> String
}

protocol Pet: Animal {
    var name: String { get }
    func play() -> String
}

class Dog: Pet {
    var species: String
    var name: String
    
    init(species: String, name: String) {
        self.species = species
        self.name = name
    }
    
    func makeSound() -> String {
        return "🐶woo~"
    }
    
    func play() -> String {
        return "\(name) 在玩呢"
    }
}

使用:

let myDog = Dog(species: "犬科", name: "大黄")
print(myDog.species)           // 犬科
print(myDog.makeSound())       // 🐶woo~
print(myDog.name)              // 大黄
print(myDog.play())            // 大黄 在玩呢

在这个示例中

  • Pet 协议继承了 Animal 协议,因此任何遵循 Pet 协议的类型也必须实现 Animal 协议中的要求。

  • Dog 类实现了 Pet 协议,因此它需要实现 Animal 和 Pet 协议中的所有属性和方法。


八、协议的组合

协议组合允许你在某个类型需要 同时遵循多个协议 时,将多个协议组合在一起使用。

用符号 & 连接,用逗号分隔。

示例

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("恭喜\(celebrator.name)\(celebrator.age)岁生日快乐!")
}
let person = Person(name: "小明", age: 20)
wishHappyBirthday(to: person)       // 恭喜小明20岁生日快乐!

九、协议的扩展

协议可以通过扩展来提供方法和属性的实现; 这就允许你在协议中自定义行为;

例如,RandomNumberGenerator 协议可以扩展来提供 randomBool() 方法。

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}

十、协议遵循的检查

使用 isas 运算符来检查协议遵循。

  • is 运算符返回truefalse
  • as? 不遵循这个协议的话值就是 nil
  • as! 强制转换失败 触发运行时错误
if dog is Animal {
    print("遵守了Animal协议")
}

if let dog = object as? Animal {
    print("遵守了Animal协议")
} else {
    print("dog = nil")
}

// 如果强制转换失败 **触发运行时错误**
if let dog = object as! Animal {
    print("遵守了Animal协议")
}


十一、协议的可选实现

在 Swift 中,协议中的要求通常是强制性的,但有时我们希望某些协议要求是 可选的

为此,Swift提供了两个主要的方法:

  1. 通过 Objective-C 的特性实现可选协议要求
  2. 在协议扩展中提供默认实现;
使用Objective-C特性实现可选协议要求

将协议标记为 @objc optional,这种方法只能在 中使用(因为Objective-C特性 不支持 结构体 或 枚举)。

示例

@objc protocol OptionalProtocol {
    var requiredProperty: String { get }
    func requiredMethod()
    
    // 可选属性和方法
    @objc optional var optionalProperty: String? { get }
    @objc optional func optionalMethod()
}

class MyClass: OptionalProtocol {
    var requiredProperty: String = "requiredProperty"
    
    func requiredMethod() {
        print("required Method")
    }
    
    // 可选方法,可以选择不实现
    func optionalMethod() {
        print("optional Method")
    }
}
let myObject = MyClass()
print(myObject.requiredProperty)    // requiredProperty
myObject.requiredMethod()           // required Method
myObject.optionalMethod()           // optional Method
在协议扩展中提供默认实现
protocol DefaultProtocol {
    func requiredMethod()
    func optionalMethod()
    
    var optionalProperty: String { get }
}

extension DefaultProtocol {
    func optionalMethod() {
        print("扩展实现了 optionalMethod")
    }
    
    var optionalProperty: String {
        return "扩展实现了 optionalProperty"
    }
}

class MyClass: DefaultProtocol {
    
    func requiredMethod() {
        print("实现了 requiredMethod")
    }
    
    // 可选方法和属性可以选择重写默认实现
    func optionalMethod() {
        print("重写默认实现 optionalMethod")
    }
    
    var optionalProperty: String = "重写默认实现 optionalProperty"
}
let myObject = MyClass()
myObject.requiredMethod()    // 实现了 requiredMethod
myObject.optionalMethod()    // 重写默认实现 optionalMethod
print(myObject.optionalProperty)   // 重写默认实现 optionalProperty

参考Apple官方Swift教程:点击链接