Swift - 协议

150 阅读8分钟

异变方法要求

有时需要再方法中改变方法所属的实例,将mutating关键字作为方法的前缀,写在func关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值。

注意:

实现协议中的mutating方法时,若是类类型,则不用写mutating关键字。而对于结构体和枚举,则必须写mutating关键字

protocol Togglable {
    mutating func toggle()
}

构造器要求

协议可以要求遵循协议的类型实现指定的构造器。可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写括号和构造器的实体

protocol SomeProtocol {
    init(someParameter: Int)
}

协议构造器要求的类实现

在遵循的协议类中实现构造器,无论是指定构造器还是便利构造器,都需要在构造器添加required修饰符

class SomeClass: SomeProtocol {
    required init(someParameter: Int) {
        // 这里是构造器的实现部分
    }
}

使用 required 修饰符可以确保所有子类也必须提供此构造器实现,从而也能遵循协议;

注意

如果类已经被标记为final,那么不需要在协议构造器的实现中使用required修饰符,因为final类不能有子类。

如果子类重写了父类的指定构造器,并且该构造器满足了该协议的要求,那么在该构造器前面需要使用requiredoverride修饰符

可失败构造器的要求

协议还可以遵循协议的类型定义 可失败构造器要求;

遵循协议的类型可以通过可失败构造器init?)或非可失败构造器init)来满足协议中定义的可失败构造器要求。

协议还可以为遵循协议的类型定义可失败构造器要求,详见 可失败构造器

遵循协议的类型可以通过可失败构造器(init?)或非可失败构造器(init)来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(init)或隐式解包可失败构造器(init!)来满足。

协议作为类型

协议作为类型使用,有时被称作为【存在类型】,这个名次来自【存在着一个类型T,该类型遵循协议T】;

协议可以像其他普通类型一样使用,使用场景如下:

  • 作为函数、方法或构造器中的参数类型或返回值类型
  • 作为常量、变量或属性的类型
  • 作为数组、字典或其他容器中的元素类型

委托

代理;

protocol DiceGame {
    var dice: Dice { get }
    func play()
}

protocol DiceGameDelegate {
    func gameDidStart(_ game: DiceGame)
}

class SnakesAndLadders: DiceGame {
    init() {
      ...
    }
    var delegate: DiceGameDelegate?
    func play() {
        delegate?.gameDidStart(self)
    }
}

class DiceGameTracker: DiceGameDelegate {
    func gameDidStart(_ game: DiceGame) {
       ...
    }
}

let tracker = DiceGameTracker()
let game = SnakesAndLadders()
game.delegate = tracker
game.play()

在扩展里添加协议遵循

注意:

通过扩展令已有类型遵循并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。

extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

protocol TextRepresentable {
    var textualDescription: String { get }
}

extension SnakesAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}

有条件地遵循协议

泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过在扩展类型时列出限制让泛型类型有条件地遵循某协议,关键词:where

extension Array: TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        let itemsAsText = self.map { $0.textualDescription }
        return "[" + itemsAsText.joined(separator: ", ") + "]"
    }
}
let myDice = [d6, d12]
print(myDice.textualDescription)
// 打印 "[A 6-sided dice, A 12-sided dice]"

在扩展里声明采纳协议

在一个类型已经遵循了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空的拓展来让他采纳该协议

struct Hamster {
	var name: String
   	var textualDescription: String {
		return "A hamster named \(name)"
	}
}
extension Hamster: TextRepresentable {}

注意:

即使满足了协议的所有要求,类型也不会自动遵循协议,必须显示地遵循协议

使用合成实现来采纳协议

Swift 可以自动提供一些简单场景下遵循EquatableHashableComparable协议的实现。在使用这些合成实现之后,无需再编写重复的代码来实现这些协议所要求的方法。

Swift为以下几种自定义类型提供了Equatable协议的合成实现:

  • 遵循Equatable协议且只有存储属性的结构体;
  • 遵循Equatable协议且只有关联类型的枚举;
  • 没有任何关联类型的枚举

swiftgg.gitbook.io/swift/swift…

协议类型的集合

协议类型可以在数组或字典这样的集合中使用:

let things: [TextRepresentable] = [game, d12, simonTheHamster]

协议的继承

协议能够继承一个或多个其他协议,可以在继承的协议基础上新增要求,语法和类的继承类似,多个协议用逗号隔开:

protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
	// 这里是协议的定义部分
}

类专属的协议

通过添加AnyObject关键字道协议的集成列表,就可以限制协议只能被类类型遵循(而不是结构体类型或枚举类型)。

protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {

// 这里是类专属协议的定义部分

}

你通过添加 AnyObject 关键字到协议的继承列表,就可以限制协议只能被类类型遵循(而不能是结构体类型或者枚举类型)。

协议合成

要求一个类型同时遵循多个协议是很有用的。可以使用协议组合来复合多个协议道一个要求里面。协议组合不定义任何新的协议类型。

协议组合使用SomeProtocol&AnotherPrototcol的形式。可以列举任意数量的协议,用&符号分开。除了协议列表,协议组合也能包含类类型,这允许你标明一个需要的父类

协议&协议

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 birthdayPerson = Person(name: "Malcolm", age: 21)
wishHappyBirthday(to: birthdayPerson)
// 打印 “Happy birthday Malcolm - you're 21!”

类类型&协议

class Location {
    var latitude: Double
    var longitude: Double
    init(latitude: Double, longitude: Double) {
        self.latitude = latitude
        self.longitude = longitude
    }
}
class City: Location, Named {
    var name: String
    init(name: String, latitude: Double, longitude: Double) {
        self.name = name
        super.init(latitude: latitude, longitude: longitude)
    }
}
func beginConcert(in location: Location & Named) {
    print("Hello, \(location.name)!")
}
 
let seattle = City(name: "Seattle", latitude: 47.6, longitude: -122.3)
beginConcert(in: seattle)
// 打印 "Hello, Seattle!"

检查协议的一致性

可以使用类型转换中描述的isas操作符来检查协议的一致性,即是否遵循某协议,并且可以转换到指定的协议类型。检查和转换协议的语法与检查和转换类型是完全一样的。

  • is 用来检查实例是否遵循某个协议,若遵循则返回 true,否则返回 false
  • as? 返回一个可选值,当实例遵循某个协议时,返回类型为协议类型的可选值,否则返回 nil
  • as! 将实例强制向下转换到某个协议类型,如果强转失败,将触发运行时错误。

可选协议的要求

协议可以定义可选要求,遵循协议的类型可以选择是否实现这些要求。在协议中使用optional关键字作为前缀来定义可选要求。协议和可选要求都必须带上@objc属性。

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

协议扩展

协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。通过这种方式,可以基于协议本身来实现这些功能,而无需在每个遵循协议协议的类型中都重复同样的实现,也无需使用全局函数。

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

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

通过协议扩展,所有遵循协议的类型,都能自动获得这个扩展所增加的方法实现而无需任何额外修改;

提供默认实现

可以通过协议扩展来为协议要求的方法、计算属性提供默认的实现。如果遵循协议的类型为提供了自己的实现,那么就会替代默认实现。

extension PrettyTextRepresentable  {
    var prettyTextualDescription: String {
        return textualDescription
    }
}

为协议扩展提供限制条件

在扩展协议时,可以指定限制条件,只有遵循协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用where子句来描述,正如 泛型 Where 子句 中所描述的。

例如,扩展Collection协议,适用于集合中的元素遵循了Equatable协议的情况。过限制集合元素遵循 Equatable 协议, 作为标准库的一部分, 你可以使用 ==!= 操作符来检查两个元素的等价性和非等价性。

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

注意:

如果一个遵循的类型满足了为同一方法或属性提供实现的多个限制型扩展的要求, Swift 会使用最匹配限制的实现