[iOS/Swift] アプリ開発の実務的アプローチで学ぶデザインパターン ～Mediator～

この記事シリーズは、iOS/Swiftエンジニアである執筆者個人が、
**ごく普通のiOSアプリ開発でよくある状況**や
**Swiftのコアライブラリやフレームワークで使われているパターン**に
着目してデザインパターンを学び直してみた記録です。

**関連記事一覧**
[[iOS/Swift] アプリ開発の実務的アプローチで学ぶデザインパターン](https://qiita.com/y-some/items/96db5f24ded4a1fab48f)

# Mediatorパターン概要

- Mediatorとは「仲介者」という意味です。
- **複数のオブジェクト間で直接やり取りをせずにMediatorを介してやり取りします。**
- 各オブジェクトが依存する相手をMediatorだけにすることで、オブジェクト同士が疎結合になり、関連オブジェクトが多い場合には保守性を向上できます。
- GoFのデザインパターンでは**振る舞いに関するパターン**に分類されます。

# 使い所

実務的な例としては、**UIPageViewControllerの配下にある子ViewController同士の通知**が考えられます。
以下のサンプルコードは、赤背景のViewControllerが非表示になる時に、青背景のViewControllerに通知を行う例です。

![qiita20200119.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/308d523a-6bac-3032-fe14-09b697dc8a93.gif)

UIPageViewControllerがMediatorの役割を担っています。
メリットは、新たにReceiverとなる子ViewControllerが増えた時、既存の子ViewControllerは変更しなくても済む点です。

# サンプルコード

Xcode 11.3でシングルページアプリケーションを新規作成し、ViewController.swiftに以下のコードをコピペすれば動作します。

```swift
// MARK: - プロトコル
protocol Receiver {
    func receive(message: String)
}

protocol Sender {
    func send(message: String)
}

protocol Mediator: class {
    var recipients: [Receiver] { get }
    func send(message: String)
}

// MARK: - PageViewControllerの子ViewController
// 送り手のViewController
final class SenderViewController: UIViewController {
    // Message送信をMediatorに委譲する
    weak var messageDelegate: Mediator?

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        // 表示されなくなった時にMessageを送信（Mediatorに委譲）
        messageDelegate?.send(message: "SenderViewController.viewDidDisappear()")
    }
}

// 受け手のViewController ※Receiverプロトコルに準拠
final class RecieverViewController: UIViewController, Receiver {
    func receive(message: String) {
        print("\(message)を受信しました")
    }
}

// MARK: - PageViewController
final class ViewController: UIPageViewController {
    // 送り手のViewController
    let senderViewController = SenderViewController()
    // 受け手のViewController
    let receiverViewController = RecieverViewController()
    // 子ViewController配列
    var controllers = [UIViewController]()

    override func viewDidLoad() {
        super.viewDidLoad()
        // PageViewControllerの子ViewControllerを設定
        senderViewController.view.backgroundColor = .red
        controllers.append(senderViewController)

        receiverViewController.view.backgroundColor = .blue
        controllers.append(receiverViewController)

        setViewControllers([controllers[0]], direction: .forward, animated: false, completion: nil)
        dataSource = self
        
        // SenderViewControllerの委譲先に自分を設定する
        senderViewController.messageDelegate = self
    }
}

// PageViewControllerをMediatorプロトコルに準拠
extension ViewController: Mediator {
    var recipients: [Receiver] {
        // 子ViewControllerの中でReceiverプロトコルに準拠しているものを返す
        return controllers.filter { $0 is Receiver } as! [Receiver]
    }

    func send(message: String) {
        for recipient in recipients {
            recipient.receive(message: message)
        }
    }
}
```

<details><summary>※説明には無関係ですが動かす時にコピペが必要なコード</summary><div>

```swift
// UIPageViewControllerDataSource
extension ViewController: UIPageViewControllerDataSource {
    // 右にスワイプ（戻る）
    func pageViewController(_ pageViewController: UIPageViewController,
                            viewControllerBefore viewController: UIViewController) -> UIViewController? {
        guard
            let index = controllers.firstIndex(of: viewController),
            index > 0
            else {
                return nil
        }
        return controllers[index - 1]
    }

    // 左にスワイプ（進む）
    func pageViewController(_ pageViewController: UIPageViewController,
                            viewControllerAfter viewController: UIViewController) -> UIViewController? {
        guard
            let index = controllers.firstIndex(of: viewController),
            index < controllers.count - 1
            else {
                return nil
        }
        return controllers[index + 1]
    }

    func presentationCount(for pageViewController: UIPageViewController) -> Int {
        return controllers.count
    }
}
```
</div></details>