デリゲーションは、クラスや構造体が別の型のインスタンスに責務を渡す(デリゲートする)ことができるようにするデザインパターンです。このデザインパターンは、準拠する型がデリゲートされた機能を提供することを保証するよう、デリゲートされる責務についてのプロトコルを定義して実装します。特定のアクションに対応させるために、あるいは外部のソースの型について知ることなくそのソースからデータを取得するために、デリゲーションを使用することができます。
次の例では、サイコロベースのボードゲームで使用する 2 つのプロトコルを定義しています。
protocol DiceGame {
var dice: Dice { get }
func play()
}
protocol DiceGameDelegate {
func gameDidStart(game: DiceGame)
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
func gameDidEnd(game: DiceGame)
}
DiceGame
プロトコルは、サイコロを使うあらゆるゲームで採用できるプロトコルです。DiceGameDelegate
プロトコルは、DiceGame
の進行を型が追跡するために採用することができます。
次は、Control Flow で紹介した蛇と梯子のゲームです。このバージョンでは、サイコロを振るのに Dice
インスタンスを使用し、DiceGame
プロトコルを採用し、進行について DiceGameDelegate
に通知しています。
class SnakesAndLadders: DiceGame {
let finalSquare = 25
let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
var square = 0
var board: [Int]
init() {
board = [Int](count: finalSquare + 1, repeatedValue: 0)
board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
}
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)
}
}
蛇と梯子ゲームの説明は、Control Flow の Break を確認してください。
ゲームのこのバージョンは、DiceGame
プロトコルを採用する SnakesAndLadders
クラスとしてまとめられています。プロトコルに準拠するために、gettable な dice
プロパティと play()
メソッドがあります。(dice
プロパティは、初期化後に変更する必要が無いのと、プロトコルが gettable を要件としているため、定数プロパティとして宣言されています。)
蛇と梯子ゲームのボードのセットアップは、クラスの init()
イニシャライザ内で行われています。ゲームロジックはすべてプロトコルの play
メソッド内に移動されており、サイコロの出目を得るために、プロトコル要件の dice
プロパティを使用しています。
ゲームを進めるためにデリゲートが必要ということではないため、delegate
プロパティはオプショナルの DiceGameDelegate
として定義されていることに注目してください。オプショナル型であるため、delegate
プロパティは自動的に初期値の nil
に設定されます。後でプロパティに適切なデリゲートを設定することができます。
DiceGameDelegate
には、ゲームの進行を追跡する 3 つのメソッドがあります。これらのメソッドは、play()
メソッド内のゲームロジックに組み込まれていて、ゲーム開始時、新しいターンの開始時、そしてゲーム終了時に呼び出されます。
delegate
プロパティはオプショナルの DiceGameDelegate
であるため、play()
メソッドはデリゲートのメソッドを呼び出すたびにオプショナルチェーンを使用しています。delegate
プロパティが nil
の場合、デリゲートの呼び出しはエラーにならずに失敗します。delegate
プロパティが nil
でない場合には、デリゲートメソッドが呼び出され、パラメータとして SnakesAndLadders
インスタンスが渡されます。
次の例は、DiceGameDelegate
プロトコルを採用する DiceGameTracker
クラスを示しています。
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")
}
}
DiceGameTracker
は、DiceGameDelegate
の要件である 3 つすべてのメソッドを実装しています。ゲームのターン数を記録するために、これらのメソッドを使用しています。ゲーム開始時に numberOfTurns
プロパティをゼロにリセットし、新しいターンの開始時に増加させ、ゲーム終了時に合計ターン数を出力します。
gameDidStart
の実装では、game
パラメータを使用してプレイ中のゲームについての紹介を出力します。game
パラメータは DiceGame
型で、SnakesAndLadders
型ではないため、gameDidStart
では DiceGame
プロトコルでの一部として実装されたメソッドとプロパティにのみアクセスして使用することができます。しかしながら、型キャストを使用してインスタンスのそもそもの型を問い合わせることができます。この例では、game
が実際には SnakesAndLadders
のインスタンスかを確認し、そうである場合には適切なメッセージを出力します。
また、gameDidStart
は渡された game
パラメータの dice
プロパティにアクセスします。game
は DiceGame
プロトコルに準拠していることがわかっているため、dice
プロパティがあることが保証されており、どのような種類のゲームをしているかにかかわらず、gameDidStart(_:)
メソッドがサイコロの 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
Portions of this page are translations based on work created and shared by Apple and used according to terms described in the Creative Commons Attribution 4.0 International License.