この記事シリーズは、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
}
}