---
title: "【译】Swift Delegation"
description: "swift 委托(Delegation)是一种设计模式，能够让类或者结构体将自己一部分责任移交(或者称之为委托delegate)给另一个类型实例。该设计模式首先定义一个协议(protocol)描述被委托的责任，然后由受托者保证委托功能的实现。委托设计模式能够用来响应特定的动作或者从其他来源接受数据而不需要知道对方的类型。"
url: "https://hicc.pro/p/swift/swift-delegation"
author: "tyler wang"
published: "2018-08-08T06:53:29.000Z"
updated: "2021-07-03T09:49:26.000Z"
category: "Swift"
tags:
  - "Swift"
  - "Swift Delegation"
  - "Swift Protocol"
  - "Delegation"
  - "Protocol"
image: "https://image.hicc.pro/pro/20210703174901FmDkR_X2CG36su9DAdvaJTlYKKOpQUZP5m87iQrEUQzbXE-8C.jpg"
lang: "zh-CN"
site: "Hipo日志"
---

# 【译】Swift Delegation

Delegation委托是Apple iOS开发中很常见的一种模式，不过在之前开发[Hipo](https://hipo.cash)中更多算是照猫画虎，这次来一篇Apple iBooks中[《The Swift Programming Language (
Swift 4.2 beta)》](https://itunes.apple.com/us/book/the-swift-programming-language-swift-4-2-beta/id1002622538?mt=11)中Protocol -- Delegation的译文。

----

委托(Delegation)是一种设计模式，能够让类或者结构体将自己一部分责任移交(或者称之为委托delegate)给另一个类型实例。该设计模式首先定义一个协议(protocol)描述被委托的责任，然后由受托者保证委托功能的实现。委托设计模式能够用来响应特定的动作或者从其他来源接受数据而不需要知道对方的类型。

下面例子为骰子积分榜类游戏定义了两个协议

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

protocol DiceGameDelegate: AnyObject {
    func gameDidStart(_ game: DiceGame)
    func game(_ game: DiceGame, didStartNewTurnWithDiceRoll: Int)
    func gameDidEnd(_ game: DiceGame)
}
```

`DiceGame`协议可以用在任何骰子类游戏。

`DiceGameDelegate`协议则可以用来追踪`DiceGame`的进度。为避免强引用的环形依赖，委托被声明为弱引用。更多弱引用的介绍可以查看[Strong Reference Cycles Between Class Instances]()。作为一个只能用在类上的协议，下面例子中`SnakesAndLadders`类只能用弱引用来声明它的委托。 一个协议继承`AnyObject`之后就只能用在类上面了，更多信息可以查看[Class-Only Protocols]()。

下面版本的*Snakes and Ladders*游戏代码最初在`Control Flow`中介绍过。这个版本采用了`DiceGame`协议，使用`Dic`实例做它的骰子机，并且将它的进度通知给`DiceGameDelegate`。
    
```swift
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?
    
    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)
    }
}
```

对于*Snakes and Ladders*游戏介绍，见[Break]()。

这个版本的游戏使用采用了`DiceGame`协议的`SnakesAndLadders`类来实现。它实现了一个可取值属性`dice`和一个方法`play()`来遵循协议(属性`dice`声明为常量，是因为它在初始化之后就不再更改了，并且协议中只规定它必须可取值)。

游戏的计分卡在类的`init()`中设置了。所有的游戏逻辑移到了协议的`play`方法中，并且使用了协议所规定的`dice`属性来提供骰子的值。

注意，`delegate`属性被定义为optional的`DiceGameDelegate`。是因为这个协议不是玩游戏所必须的。它是optional类型的，`delegate`属性自动设置初始值位`nil`。因此游戏的实例化使用者可以选择给它设置一个合适的委托。`DiceGameDelegate`协议是class-only的，你可以声明这个委托为`weak`引用类避免环形依赖。
    
`DiceGameDelegate`协议提供三个方法来跟踪游戏的进度。并且这三个方法已经在上述的`play`方法中集成了。它们会在新游戏开始，新一轮开始，或者游戏结束时候调用。

因为`delegate`属性可能是(optional)`DiceGameDelegate`。`play()`方法每次使用optional chaining来调用委托的方法。如果`delegate`属性是nil值，那么调用这些委托方法会优雅的失败而不会报错。如果`delegate`属性不是nil的值，当委托方法被调用的时候`SnakesAndLadders`的实例会被当作参数传进去。

下面例子`DiceCgameTracker`类实现了`DiceGameDelegte`协议。

```swift
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 lated for \(numberOfTurns) turns")
    }
}
```

`DiceGameTracker`实现了`DiceGameDelegate`所要求的三个方法。并且使用这三个方法来记录游戏进行了多少轮。它在游戏开始后重置`numberOfTurns`为0，每次新一轮开始后递增，并且一旦游戏结束就打印出游戏总共进行了多少轮。

上述`gameDidStart(_:)`的实现中，使用`game`参数来打印所玩游戏的介绍性信息。`game`的类型是`DiceGame`而不是`SnakesandLadders`，因此`gameDidStart(_:)`只能访问和使用`DiceGame`协议中的属性和方法。不过仍然能够使用类型推断(type casting)来判断实例的类。在这个例子中。它检查了如果`game`是`SnakesandLadders`的实例，就打印一些信息。

`gameDidStart(_:)`方法中还访问了`game`参数的`dice`属性。因为`game`实现了`DiceGame`协议，因此就保证了一定有`dice`属性。同样`gameDidStart(_:)`方法也能够访问dice的`sides`属性，而不用关心具体玩的那个游戏。

下面就是`DiceGameTracker`使用时候：

```
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”
```

---
下面代码的高亮截图，
![Teampaper-Snap_2018-08-08-18-57-14@2x](https://image.hicc.pro/hicc_me-417428029_Teampaper-Snap_2018-08-08-18-57-14@2x.png)

![Teampaper-Snap_2018-08-08-18-57-47@2x](https://image.hicc.pro/hicc_me-611360413_Teampaper-Snap_2018-08-08-18-57-47@2x.png)

![Teampaper-Snap_2018-08-08-18-58-10@2x](https://image.hicc.pro/hicc_me-595686136_Teampaper-Snap_2018-08-08-18-58-10@2x.png)

---

原文 / Source: <https://hicc.pro/p/swift/swift-delegation>
