Swift with Majid

デリゲートデザインパターンの力

29May2019

先週WWDCの前に、そして誰もが数日後に新しい機能に興奮しています。 しかし、来週のWWDCに関連する記事を残しておきましょう。 今週は私のお気に入りのデザインパターンデレゲートについてお話します。 デリゲートは、最も簡単で強力なパターンです。

ソフトウェア工学では、委任パターンはオブジェクト指向の設計パターンであり、オブジェクトの構成が継承と同じコードの再利用を実現できます。 委任では、オブジェクトは2番目のオブジェクト(委任)に委任することによって要求を処理します。 デリゲートはヘルパーオブジェクトですが、元のコンテキストを持ちます。

私たちは毎日デリゲートパターンを使用しており、iOS SDKは多くの場所でそれを使用しています。 たとえば、UITableViewは、テーブルにセルを移入するUITableViewDataSourceに委譲し、セル選択やその他のアクションもUITableViewDelegateに委譲します。 デリゲートパターのもう一つの優れた例は、FlowControllerまたはCoordinatorです。 ViewControllersはナビゲーションロジックをCoordinatorに委譲します。 ナビゲーションロジックをFlowControllersに抽出することについての投稿を分離しました。

コードサンプルに飛び込みましょう。 あなたがゲームに取り組んでいると仮定します。 ゲームロジックを分離されたクラスGameに抽出し、このゲームをレンダリングするUIViewControllerにゲームの状態の変更を委任したいとします。

protocol GameDelegate: AnyObject { func stateChanged(from oldState: Game.State, to newState: Game.State)}class Game { private var state: State = .notStarted { didSet { delegate?.stateChanged(from: oldValue, to: state) } } weak var delegate: GameDelegate? private(set) var value: Int = 0 func start() { state = .started } func generateNextValue() { value = Int.random(in: 0..<1000) state = generateState(using: value) }}extension Game { enum State { case notStarted case started case right case win case lost }}

ここでは、ランダムな値を生成する単純なゲームのソースコードです。 ゲームエンジンは、ランダムな値に基づいて状態を生成します。 すべての状態変更呼び出しは、古い状態と新しい状態を渡すためにデリゲートします。 AnyObjectから拡張されたデリゲートプロトコルを定義します。 また、weakキーワードを使用して変数保持デリゲートを定義します。 Delegateとgameクラスの間の保持サイクルを破る必要がありました。 今GameViewControllerを見てみましょう。

class GameViewController: UIViewController { private let game: Game init(game: Game) { self.game = game super.init(nibName: nil, bundle: nil) } @IBAction func play() { game.start() } @IBAction func next() { game.generateNextValue() } override func viewDidLoad() { super.viewDidLoad() game.delegate = self }}extension GameViewController: GameDelegate { func render(_ state: Game.State) { switch state { case .lost: renderLost() case .right: renderRight() case .win: renderWin() case .started: renderStart() case .notStarted: renderNotStarted() } } func stateChanged(from oldState: Game.State, to newState: Game.State) { render(newState) }}

ここにはGameViewControllerクラスがあり、ユーザーアクションをゲームに供給し、状態の変更をレンダリングします。 GameViewControllerはGameDelegateに準拠し、拡張機能で必要なすべてのレンダリングを実装します。 その結果、私たちはデリゲートデザインパターンの助けを借りて合成可能なコードベースを持っています。

クロージャ

デリゲートにメソッドが1つしかない場合は、クロージャに置き換えることができます。 考え方は同じですが、今ではクロージャを呼び出して、プロトコルでメソッドを呼び出す代わりに状態を渡します。 Closureを使用した例を見てみましょう。

class Game { typealias StateHandler = (State) -> Void var handler: StateHandler? private var state: State = .notStarted { didSet { handler?(state) } }}class GameViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() game.handler = { state in self?.render(state) } }}

ご覧のとおり、状態の変更を処理するgameクラスインスタンスにクロージャを渡します。 Weakを使用して、closureのcontext capture中にretainサイクルを中断します。 ここでの別のオプションは、Swift関数がクロージャであるという事実の使用法です。 そのため、分離されたクロージャを作成する代わりに、関数名を渡すことができます。 ただし、このメソッドはretain circleを作成するように注意してくださ これが私たちがそれを行う方法の例です。

class GameViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() game.handler = render }}extension GameViewController { func render(_ state: Game.State) { switch state { case .lost: renderLost() case .right: renderRight() case .win: renderWin() case .started: renderStart() case .notStarted: renderNotStarted() } }}

結論

今日は、iOS開発で最も強力で簡単なデザインパターンについて議論しました。 私はそれがいかにシンプルであり、コードベースをデカップリングするために作品を構成する際にどのように有用であるかを楽 Twitterで私に従うと、この記事に関連するご質問をすること自由に感じます。 読んでくれてありがとう、来週お会いしましょう!



+