Swift從入門到精通第十八篇 – 協議 初識

  • 2019 年 10 月 3 日
  • 筆記

協議(學習筆記)

環境Xcode 11.0 beta4 swift 5.1

  • 協議語法
    • 示例

      protocol SomeProtocol {      // protocol definition goes here  }  struct SomeStructure: FirstProtocol, AnotherProtocol {      // structure definition goes here  }  class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {      // class definition goes here  }
  • 屬性要求
    • 協議不需指定屬性是存儲或計算屬性,只需指定所需的屬性名稱和類型;協議指定了每個屬性是可讀或可讀可寫,通常在類型後跟 {get set}{ get } 標識

      protocol SomeProtocol {      var mustBeSettable: Int { get set }      var doesNotNeedToBeSettable: Int { get }  }
    • 協議中類型屬性仍然用關鍵字 staticclass 修飾

      protocol AnotherProtocol {      static var someTypeProperty: Int { get set }  }
      // 只有一個屬性的協議  protocol FullyNamed {      var fullName: String { get }  }  // 遵守協議必需要提供一個一個名為 fullName 的可獲取實例屬性  struct Person: FullyNamed {      var fullName: String  }  let john = Person(fullName: "John Appleseed")  // john.fullName is "John Appleseed"  //  // 遵守 FullNamed 協議,此處用的是一個只可讀的計算屬性  class Starship: FullyNamed {      var prefix: String?      var name: String      init(name: String, prefix: String? = nil) {          self.name = name          self.prefix = prefix      }      var fullName: String {          return (prefix != nil ? prefix! + " " : "") + name      }  }  var ncc1701 = Starship(name: "Enterprise", prefix: "USS")  // ncc1701.fullName is "USS Enterprise"
  • 方法要求
    • 協議定義中可定義實例和類型方法,與正常的方法定義類似,方法參數可以是可變參數,但不能有方法體,且參數不能設置默認值

      protocol SomeProtocol {      static func someTypeMethod()  }  protocol RandomNumberGenerator {      func random() -> Double  }  // 示例如下  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      }  }  let generator = LinearCongruentialGenerator()  print("Here's a random number: (generator.random())")  // Prints "Here's a random number: 0.3746499199817101"  print("And another one: (generator.random())")  // Prints "And another one: 0.729023776863283"
  • 可變方法要求
    • 協議中方法可用 mutatuing 修飾,這位遵守此協議的方法可以修改屬性值(結構體、枚舉也可以),且可在實現方法時可省略此關鍵字

      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  lightSwitch.toggle()  // lightSwitch is now equal to .on
  • 初始化器要求
    • 協議中可定義初始化器讓遵守的類型實現,但協議中的初始化器沒有 {} 或 函數體,實現類實現初始化器(便捷或指定初始化器)必需加 required 修飾符

      protocol SomeProtocol {      init(someParameter: Int)  }  class SomeClass: SomeProtocol {      required init(someParameter: Int) {          // initializer implementation goes here      }  }
    • 如果一個子類重寫父類的指定初始化器同時遵守協議的初始化器的要求,必需同時用 requiredoverride 修飾符

      protocol SomeProtocol {      init()  }  //  class SomeSuperClass {      init() {          // initializer implementation goes here      }  }  //  class SomeSubClass: SomeSuperClass, SomeProtocol {      // "required" from SomeProtocol conformance; "override" from SomeSuperClass      required override init() {          // initializer implementation goes here      }  }
  • 協議作為類型
    • 可以做參數類型、函數返回值、方法返回值、初始化器返回值類型
    • 可以做常量、變數、屬性的類型
    • 可以用在字典、數組、其它容器中元素的類型

      class Dice {      let sides: Int      let generator: 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
  • 代理
    • 代理是一種重要的設計模式,示例程式碼

      protocol DiceGame {      var dice: Dice { get }      func play()  }  protocol DiceGameDelegate: AnyObject {      func gameDidStart(_ game: DiceGame)      func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)      func gameDidEnd(_ game: DiceGame)  }  class SnakesAndLadders: DiceGame {      let finalSquare = 25      let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())      var square = 0      var board: [Int]      init() {          board = Array(repeating: 0, count: finalSquare + 1)          board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02          board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08      }      weak var delegate: DiceGameDelegate? // 一般用 weak      func play() {          square = 0          delegate?.gameDidStart(self)          gameLoop: while square != finalSquare {              let diceRoll = dice.roll()              delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)              switch square + diceRoll {              case finalSquare:                  break gameLoop              case let newSquare where newSquare > finalSquare:                  continue gameLoop              default:                  square += diceRoll                  square += board[square]              }          }          delegate?.gameDidEnd(self)      }  }  class DiceGameTracker: DiceGameDelegate {      var numberOfTurns = 0      func gameDidStart(_ game: DiceGame) {          numberOfTurns = 0          if game is SnakesAndLadders {              print("Started a new game of Snakes and Ladders")          }          print("The game is using a (game.dice.sides)-sided dice")      }      func game(_ game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int) {          numberOfTurns += 1          print("Rolled a (diceRoll)")      }      func gameDidEnd(_ game: DiceGame) {          print("The game lasted for (numberOfTurns) turns")      }  }  let tracker = DiceGameTracker()  let game = SnakesAndLadders()  game.delegate = tracker  game.play()  // Started a new game of Snakes and Ladders  // The game is using a 6-sided dice  // Rolled a 3  // Rolled a 5  // Rolled a 4  // Rolled a 5  // The game lasted for 4 turns
  • 用擴展添加協議的一致性
    • 擴展可以為現有的類添加屬性、方法、下標,因此可以添加協議可能需要的任何要求

      protocol TextRepresentable {      var textualDescription: String { get }  }  extension Dice: TextRepresentable {      var textualDescription: String {          return "A (sides)-sided dice"      }  }  let d12 = Dice(sides: 12, generator: LinearCongruentialGenerator())  print(d12.textualDescription)  // Prints "A 12-sided dice"  extension SnakesAndLadders: TextRepresentable {      var textualDescription: String {          return "A game of Snakes and Ladders with (finalSquare) squares"      }  }  print(game.textualDescription)  // Prints "A game of Snakes and Ladders with 25 squares"
    • 有條件的遵守協議

      // Array 中的元素要遵守  TextRepresentable 協議  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)  // Prints "[A 6-sided dice, A 12-sided dice]"
    • 採用擴展申明協議

      struct Hamster {      var name: String      var textualDescription: String {          return "A hamster named (name)"      }  }  extension Hamster: TextRepresentable {}  let simonTheHamster = Hamster(name: "Simon")  // 這裡要顯示申明類型  let somethingTextRepresentable: TextRepresentable = simonTheHamster  print(somethingTextRepresentable.textualDescription)  // Prints "A hamster named Simon"
  • 協議類型集合
    • 示例

      let things: [TextRepresentable] = [game, d12, simonTheHamster]  for thing in things {      print(thing.textualDescription)  }  // A game of Snakes and Ladders with 25 squares  // A 12-sided dice  // A hamster named Simon
  • 協議繼承
    • 示例

      protocol InheritingProtocol: SomeProtocol, AnotherProtocol {      // protocol definition goes here  }  protocol PrettyTextRepresentable: TextRepresentable {      var prettyTextualDescription: String { get }  }  extension SnakesAndLadders: PrettyTextRepresentable {      var prettyTextualDescription: String {          var output = textualDescription + ":n"          for index in 1...finalSquare {              switch board[index] {              case let ladder where ladder > 0:                  output += "▲ "              case let snake where snake < 0:                  output += "▼ "              default:                  output += "○ "              }          }          return output      }  }  print(game.prettyTextualDescription)  // A game of Snakes and Ladders with 25 squares:  // ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
  • Class-Only 協議
    • 可以限制協議只能類來遵守,通過添加 AnyObject 協議在繼承列表中,如果結構體或枚舉遵守會報運行時錯誤

      protocol SomeClassOnlyProtocol: AnyObject, SomeInheritedProtocol {      // class-only protocol definition goes here  }
  • 協議合成
    • & 符號將多個協議連接起來

      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)  // Prints "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)  // Prints "Hello, Seattle!"
  • 協議一致性檢查
    • is 實例遵守協議返回 true, 否則 false
    • as? 返回一個協議類型的可選值,如果不遵守協議則是 nil
    • as! 強制解包,如果失敗觸發運行時錯誤

      protocol HasArea {      var area: Double { get }  }  class Circle: HasArea {      let pi = 3.1415927      var radius: Double      var area: Double { return pi * radius * radius }      init(radius: Double) { self.radius = radius }  }  class Country: HasArea {      var area: Double      init(area: Double) { self.area = area }  }  class Animal {      var legs: Int      init(legs: Int) { self.legs = legs }  }  let objects: [AnyObject] = [      Circle(radius: 2.0),      Country(area: 243_610),      Animal(legs: 4)  ]  for object in objects {      if let objectWithArea = object as? HasArea {          print("Area is (objectWithArea.area)")      } else {          print("Something that doesn't have an area")      }  }  // Area is 12.5663708  // Area is 243610.0  // Something that doesn't have an area
  • 可選協議要求
    • optional 修飾,協議和可選需求都必需用 @objc 標記,被標記的協議只能是OC類或其它 @objc類可用,枚舉結構體不可用

      @objc protocol CounterDataSource {      @objc optional func increment(forCount count: Int) -> Int      @objc optional var fixedIncrement: Int { get }  }  class Counter {      var count = 0      var dataSource: CounterDataSource?      func increment() {          if let amount = dataSource?.increment?(forCount: count) {              count += amount          } else if let amount = dataSource?.fixedIncrement {              count += amount          }      }  }  class ThreeSource: NSObject, CounterDataSource {      let fixedIncrement = 3  }  var counter = Counter()  counter.dataSource = ThreeSource()  for _ in 1...4 {      counter.increment()      print(counter.count)  }  // 3  // 6  // 9  // 12  class TowardsZeroSource: NSObject, CounterDataSource {      func increment(forCount count: Int) -> Int {          if count == 0 {              return 0          } else if count < 0 {              return 1          } else {              return -1          }      }  }  counter.count = -4  counter.dataSource = TowardsZeroSource()  for _ in 1...5 {      counter.increment()      print(counter.count)  }  // -3  // -2  // -1  // 0  // 0
  • 協議擴展
    • 可以為協議添加擴展,擴展里可以添加方法、計算屬性、下標、初始化器等,這樣類型的一致性或全局函數的行為可在此實現

      extension RandomNumberGenerator {      func randomBool() -> Bool {          return random() > 0.5      }  }  let generator = LinearCongruentialGenerator()  print("Here's a random number: (generator.random())")  // Prints "Here's a random number: 0.3746499199817101"  print("And here's a random Boolean: (generator.randomBool())")  // Prints "And here's a random Boolean: true"
    • 提供默認的實現

      extension PrettyTextRepresentable  {      var prettyTextualDescription: String {          return textualDescription      }  }
    • 協議擴展添加約束

      extension Collection where Element: Equatable {      func allEqual() -> Bool {          for element in self {              if element != self.first {                  return false              }          }          return true      }  }  let equalNumbers = [100, 100, 100, 100, 100]  let differentNumbers = [100, 100, 200, 100, 200]  print(equalNumbers.allEqual())  // Prints "true"  print(differentNumbers.allEqual())  // Prints "false"