オブザーバパターンは1対多のイベント通知を可能にします。
例えば、1つのイベントの結果を複数のオブジェクトが知る必要がある場合があります。
そのような場合にデリゲートパターンではデリゲート先となるオブジェクトは1つで、同じイベントを複数のオブジェクトが受け取ろうとすると、その数だけデリゲート先を追加しなければなりません。
しかし、オブザーバパターンはこうした1対多のイベント通知を可能にします。
オブザーバパターンの構成要素
- ❶オブジェクト・・・オブザーバを管理し、必要なタイミングでオブザーバに通知を発行
- ❷オブザーバ(オブジェクト)・・・・通知を受ける対象(のオブジェクト)
通知は、❶オブジェクトから❷オブザーバのメソッドを呼び出すことで行われます。
Notification型とNotificationCenterクラス
iOSやmacOSアプリケーションでは、オブザーバパターンをCocoaが提供するNotification型(構造体)とNotificationCenterクラスを用いて実装します。
NotificationCenterクラス
NotificationCenterクラスを使用することで通知という仕組みを使用することができます。
※)補足:あるイベントが発生した時に、同じアプリ内の別クラスでそのイベントを拾いたいといった時や、クラスで何かの処理が終わったとき、ViewController クラスへ通知する時に便利です。
NotificationCenterクラスとの関係
● オブジェクト
このクラスを通じてオブジェクトは通知の送受信を行います。
● オブザーバ
オブザーバはこのクラスに登録され、登録の際に、通知を受け取るイベントと受け取る際に利用するメソッドを指定します。
1つのイベントに対して複数のオブザーバを登録できるので、1対多の関係のイベント通知が可能になります。
Notification型
Notification型はNotificationCenterクラスから発行される通知をカプセル化したものです。
name、object、userInfoというプロパティを持っています。
nameプロパティ
通知を特定するためのタグ情報(通知名、イベント名)
objectプロパティ
通知を送ったオブジェクト情報
userInfoプロパティ
通知に関連するそのほかの情報
オブジェクト間のイベント通知の流れ
- 通知を受け取るオブジェクト(オブザーバ)にNotification型の値を引数に持つメソッド(イベントハンドラ)を実装する。
(オブザーバ / Observerクラス) - NotificationCenterクラスに通知を受け取るオブジェクトを登録する。
(オブザーバ / Observerクラス) - NotificationCenterクラスに通知を投稿する。
(オブジェクト / Posterクラス)
オブジェクト / Posterクラス
必要なタイミングでオブザーバに通知を発行するオブジェクト
//通知を発生させるオブジェクト(オブジェクト)
class Poster {
//SomeNotification"という名前の通知(イベント)を設定
static let notificationName = Notification.Name("SomeNotification")
//❸NotificationCenterクラスに通知を投稿する。
//"SomeNotification"という名前の通知を投稿しています。
func post() {
NotificationCenter.default.post(
name: Poster.notificationName,
object: nil
)
}
}
● 4行目
通知名(イベント名)を設定。
※Notification.NameはNSNotificationクラス内の構造体。
public struct Notification : ReferenceConvertible, Equatable, Hashable {
public typealias Name = NSNotification.Name
}
open class NSNotification : NSObject, NSCopying, NSCoding {
.........
}
extension NSNotification {
public struct Name : RawRepresentable, Equatable, Hashable {
public init(_ rawValue: String)
public init(rawValue: String)
}
}
● 9行目
NotificationCenterクラスへの通知の投稿
NotificationCenter.default.post(name:object:)メソッドを用い、通知の名前(イベント名)、通知を送る側のオブジェクト(多くの場合は自分自身「self」)を渡します。nilが指定されている場合は「通知を送る側のオブジェクトは指定しない」になります。
このメソッドはNotification型のインスタンスを自動的に作成するので、自分自身で作成する必要はありません。
※)NotificationCenter.defaultはデフォルトの通知センター。
オブザーバ / Observerクラス
通知を受ける対象のオブジェクト
//通知を受け取るオブジェクト(オブザーバ)
class Observer {
init() {
//❷ NotificationCenterクラスに通知を受け取るオブジェクトを登録する
NotificationCenter.default.addObserver(
self,
selector: #selector(handleNotification(_:)),
name: Poster.notificationName,
object: nil
)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
//❶通知を受け取るオブジェクト(オブザーバ)にNotification型の値を引数に持つメソッドを実装する
@objc func handleNotification(_ notification: Notification) {
print("通知を受け取りました")
}
}
● 18行目
Notification型の値を引数に持つメソッドhandleNotification(_:)を通じて通知を受け取る。
※)handleNotification(_:)は独自メソッド(イベントハンドラ)
● 5行目
NotificationCenterクラスに通知を受け取るオブジェクトを登録する。
オブザーバのNotificationCenterクラスへの登録はaddObserver(_:selector:name:object:)メソッドを用い、通知を受け取るオブジェクト、通知を受け取った際実行されるメソッド(イベントハンドラ)、受け取りたい通知の名前(イベント名)を登録します。
NotificationCenter.default.addObserver(
self, //通知を受け取るオブジェクト
selector: #selector(handleNotification(_:)), //通知を受け取った際実行されるメソッド
name: Poster.notificationName, //受け取りたい通知の名前(イベント)
object: nil
)
SomeNotification”という名前の通知(イベント名)をObserverクラス(オブザーバ)で受け取り、handleNotification(_:)メソッドを実行するように登録しています。
※)NotificationCenter.defaultはデフォルトの通知センター。
※)Selector型については下記をご覧ください。
【swift】【ios】Selector型
● 13行目
オブザーバ(オブジェクト)が破棄されるタイミングで、そのオブザーバへの通知をやめるという処理を記述。
全体
import Foundation
//通知を発生させるオブジェクト(オブジェクト)
class Poster {
//SomeNotification"という名前の通知をやりとりする
static let notificationName = Notification.Name("SomeNotification")
//❸NotificationCenterクラスに通知を投稿する。
//"SomeNotification"という名前の通知を投稿しています。
func post() {
NotificationCenter.default.post(
name: Poster.notificationName, object: nil)
}
}
//通知を受け取るオブジェクト(オブザーバ)
class Observer {
init() {
//❷ NotificationCenterクラスに通知を受け取るオブジェクトを登録する
NotificationCenter.default.addObserver(
self,
selector: #selector(handleNotification(_:)),
name: Poster.notificationName,
object: nil
)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
//❶通知を受け取るオブジェクト(オブザーバ)にNotification型の値を引数に持つメソッドを実装する
@objc func handleNotification(_ notification: Notification) {
print("通知を受け取りました")
}
}
var observer = Observer()
let poster = Poster()
poster.post()
//実行結果
//通知を受け取りました
PosterクラスとObserverクラスをインスタンス化し、Posterクラスのpost()メソッドを呼び出すと、サブジェクトであるNotificationCenterクラスを通じて「SomeNotification」という名前の通知(イベント名)が発行されます。
Observerクラスはこの通知を受け取り、handleNotification(_:)メソッドを実行して「通知を受け取りました」というメッセージを出力しています。
複数のオブジェクトへ通知
import Foundation
//通知を発生させるオブジェクト(オブジェクト)
class Poster {
//SomeNotification"という名前の通知をやりとりする
static let notificationName = Notification.Name("SomeNotification")
//"SomeNotification"という名前の通知を投稿しています
//このメソッドが実行された(イベント)時に通知が発行される
func post() {
NotificationCenter.default.post(
name: Poster.notificationName, object: nil)
}
}
//通知を受け取るオブジェクト(オブザーバ)
class Observer {
init() {
//❷ NotificationCenterクラスに通知を受け取るオブジェクトを登録する
NotificationCenter.default.addObserver(
self,
selector: #selector(handleNotification(_:)),
name: Poster.notificationName,
object: nil
)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
//❶通知を受け取るオブジェクト(オブザーバ)にNotification型の値を引数に持つメソッドを実装する
//通知が発行された時にこのメソッドが実行される
@objc func handleNotification(_ notification: Notification) {
print("通知を受け取りました")
}
}
//通知を受け取るオブジェクト(オブザーバ)
class Observer2 {
init() {
//❷ NotificationCenterクラスに通知を受け取るオブジェクトを登録する
NotificationCenter.default.addObserver(
self,
selector: #selector(handleNotification(_:)),
name: Poster.notificationName,
object: nil
)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
//❶通知を受け取るオブジェクト(オブザーバ)にNotification型の値を引数に持つメソッドを実装する
//通知が発行された時にこのメソッドが実行される
@objc func handleNotification(_ notification: Notification) {
print("通知を受け取りました2")
}
}
var observer = Observer()
var observer2 = Observer2()
let poster = Poster()
poster.post()
//実行結果
//通知を受け取りました
//通知を受け取りました2
まとめ
1対多のイベント通知が発生する場合はオブザーバパターンを利用します。
たとえば、ユーザー情報を表示している画面が複数あり、ユーザーがプロフィールを更新するなどして、それらすべての画面を再描画する必要が生じたとします。このようなケースでは、更新する必要がある画面をすべてオブザーバとして登録しておき、プロフィールが更新されたタイミングで登録された画面すべてに通知を送り、再描画を行います。
オブザーバパターンは、どの処理がいつ実行されるかをコード上からただちに読み取ることが困難であるため、濫用はしないほうが良いと思います。
1対1の処理では、オブザーバパターンではなく、デリゲートパターンやクロージャによるコールバックを利用した方が良いと思います。