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


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

関連記事一覧
[iOS/Swift] アプリ開発の実務的アプローチで学ぶデザインパターン

Mediatorパターン概要

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

使い所

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

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

サンプルコード

Xcode 11.3でシングルページアプリケーションを新規作成し、ViewController.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)
        }
    }
}

※説明には無関係ですが動かす時にコピペが必要なコード
// 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
    }
}