この記事シリーズは、iOS/Swiftエンジニアである執筆者個人が、
ごく普通のiOSアプリ開発でよくある状況や
Swiftのコアライブラリやフレームワークで使われているパターンに
着目してデザインパターンを学び直してみた記録です。
関連記事一覧
[iOS/Swift] アプリ開発の実務的アプローチで学ぶデザインパターン
Observerパターン概要
- Observerとは「観察者」という意味です。
- オブジェクトの状態が変化した際に、そのオブジェクト自身が、「観察者」に状態の変化を「通知」する仕組みです。
- GoFのデザインパターンでは振る舞いに関するパターンに分類されます。
- iOSアプリ開発では、他のどのパターンよりも実用的なパターンと思います。
使い所
実務においては、ViewControllerにAPI通信やデータ取得/更新などのロジックを詰め込むとViewControllerが肥大化してしまい、保守性が低下します。
そこで例えば、API通信やデータ取得/更新をViewModelに追い出す「MVVMアーキテクチャー」などを導入したりします。
ViewModelからViewControllerに状態変化の通知をする際にObserverパターンを良く使います。
サンプルコード
Xcode 11.3 / Swift 5.1 です。
import UIKit
// MARK: - Imageの変化を通知するためのprotocol
protocol ImageObserver: class {
func didChange()
}
// MARK: - ViewModel
final class ViewModel {
weak var imageObserver: ImageObserver?
var image: UIImage? {
didSet {
// 値が変わったらObserverに通知
if image != oldValue {
imageObserver?.didChange()
}
}
}
/// サーバーから画像を取得する
func requestImage(with url: URL) {
URLSession.shared.dataTask(with: url) { [weak self] (data, response, error) in
if let error = error {
print("画像取得失敗: ", error)
return
}
guard let data = data, let image = UIImage(data: data) else {
return
}
self?.image = image // image.didSetが実行され、ViewController.didChange()に通知される
}.resume()
}
}
// MARK: - ViewController
class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
private let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.imageObserver = self
let imageUrl = URL(string: "https://upload.wikimedia.org/wikipedia/commons/5/56/Donald_Trump_official_portrait.jpg")!
viewModel.requestImage(with: imageUrl)
}
}
// ViewControllerをImageObserver protocolに準拠
extension ViewController: ImageObserver {
func didChange() {
DispatchQueue.main.async { [weak self] in
self?.imageView.image = self?.viewModel.image
}
}
}
補足
Property Observers
didSet
はプロパティに値がセットされた後に呼ばれる処理ブロックで、Property Observers(プロパティ・オブザーバー)と呼びます。
oldValue
という予約された変数によって、以前値を取得できます。
プロパティ・オブザーバーには、値がセットされる前に呼ばれるwillSet(_:)
もあります。
こちらは引数で新しい値を取得できます。
プロパティ・オブザーバーがあることで、上のサンプルコードで仮にViewModel.image
をセットする箇所が増えたとしても、 imageObserver.didChange()
の呼び出しを増やさずに済みます。
SwiftはObserverパターンを実装しやすい言語であると思います。
(参考リンク)
The Swift Programming Language - Properties
Observerパターン以外の通知方法
iOS/Swiftにおいては、Observerパターン以外にもオブジェクト間の通知の方法がいくつかあります。
こちらの記事が参考になります。
Swiftとオブジェクト間の通知のパターン