note6月通知

iOSアプリのしくみ・アプリ内通知

アプリの様々な機能を裏で支えるしくみについて知り、プログラミングの幅を広げてください。今回は「通知」を中心に(辞書の復習とクロージャも)解説します。
通知の実例としてダイナミックタイプに対応するコードをためしてください。

毎月札幌でiOSアプリ作りをアシストするセミナーをやっています。1時間にわたるセミナーの全内容を、物理的に参加できない方のためにnote上で公開します。ぜひアプリ作りにチャレンジしてください。

iOSアプリ作りをアシストするセミナーは今後も月一回のペースで続ける予定です。
詳細は connpass.com の 札幌Swift でご確認ください。そして機会があればぜひ参加してください。
アプリ作りプログラミング教育に関連する話題は 札幌Swift のfacebookページ で発信しています。

通知とは

通知は「Notifications」の訳です。英語でも日本語でも何かを知らせることです。

『通知とはあるオブジェクトが他のオブジェクトのメソッドを呼び出すこと
木下誠さんの本『iOS開発におけるパターンによるオートマティズム』(2011年出版)にこのように明快に書かれていました。

なぜメソッドを呼び出すことが、何かを知らせることになるのでしょう?
お知らせはそれに対応した処理を開始する合図として発信します。
そして処理はメソッドでおこないます。
ここでは処理開始の合図を発信して実際に処理を行うことまで含めて「Notifications」(通知のしくみ)と呼ぶことにします。

『通知とはあるオブジェクトが他のオブジェクトのメソッドを呼び出すこと』から、通知する場合にはメソッドを持つオブジェクトの参照が必要になります。
アウトレットのような参照を使ってメソッドを呼び出そうとすると参照だらけになってしまいます。
参照だらけの状況を避けるために使われるのが「通知」のしくみです。

ここでは画面UIとは別のもの

これから説明する Notifications は、iOSやmacに表示される通知(リマインダーやメッセージの受信などを知らせるUI)ではありません。

ロック画面やどのアプリを使っていても表示されるバナーなどの表示は UserNotifications フレームワークを使います。今回はこの話ではありません。
HIG の Notifications は UserNotifications です。

どんな通知があるのか

例えば ソフトウェアキーボード表示されると、画面の一部が下にかくれます。このためソフトウェアキーボードが表示される前や、表示された直後に通知が届きます。
この通知にはキーボードの範囲情報も取り出し可能になっていてアプリ側で入力欄をスクロールさせるなどの対応に使うことができます。

ほかに代表的なものだけでも アプリがアクティブになった/メモリ不足が発生/文字サイズ変更/連絡先変更 などほかにもたくさんあります。

通知の種類

iOSプログラミングで使われる通知は三種類あります。
❶ 1対1の通知 デリゲート
❷ 1対多の通知 KVO(Key Value Observing)
➌ 多対多の通知 NotificationCenter
これも木下誠さんの本『iOS開発におけるパターンによるオートマティズム』からの引用です。
今回は三つ目の NotificationCenter を使った通知を解説します。

デリゲートは「iOSアプリを作ろう」シリーズでも何度か登場しました。
KVO は初学者向けとは言えません。使いこなすにはアプリ作りやバグの追求スキルがある程度必要です。

NotificationCenter を使う通知

NotificationCenter を使う多対多の通知はラジオ局(放送)に例えられます。

呼び出し元(ポスター)は 通知を識別子(文字列)で区別します
呼び出し先(オブザーバー)はどの通知に対応するかを NotificationCenter に登録します。通知に対応するメソッド名も登録することで、通知が発生するとそのメソッドを NotificationCenter が呼び出します。
呼び出しには任意の情報を付加できるしくみもあるので応用範囲が広がります。

通知の具体的な使い道のひとつとして、MVC(モデル・ビュー・コントローラー)のM(モデル)からC(コントローラー)への情報伝達があります。
「データが追加された」「三番目のデータが修正された」などの通知を放送(発信)することで、コントローラーは画面を適切に更新したりできるのです。
通知を使うと、モデルはコントローラーのアウトレットなど参照用インスタンスなしで情報を伝達できます。このためモデルの独立性が高まり、再利用もしやすくなります。
更新にはモデルの情報を取得しなければなりません。通知のしくみには、内容が変わっていないのに再取得など無駄な処理を避ける効果もあります。

NotificationCenter クラス

元々はObjective-Cの NSNotificationCenter クラスです。NSNotificationCenter は macOS(OS X 10.0)の Cocoa にもあった歴史あるクラスです。

// NotificationCenter クラスの宣言
class NotificationCenter : NSObject

Swiftではクラス名の先頭から「NS」は取れましたが NSObject は継承しています。
NotificationCenterFoundationフレームワークのクラスです。UIKit フレームワークではありません。

NSNotificationCenter クラスはアプリの内部でだけ通知を配信するためのものです。ほかのアプリと通知のやり取りはできません。

呼び出し先として登録するオブジェクトを Observer(オブザーバー)と呼びます。

アプリはNotificationCenterのインスタンスをひとつ持つ

通知のしくみは重要で必須なので、すべてのアプリは NotificationCenter のインスタンスを一つだけ持っています。
このインスタンスは default タイププロパティで得ることができます。
NotificationCenter.default.addObserver( のように使います。

// NotificationCenter クラスのタイププロパティ宣言
class var `default`: NotificationCenter { get }

// 使い方 シングルコーテーションは不要
NotificationCenter.default

`default`とシングルコーテーションがついているのは「default」が Swift言語の予約語(キーワード)と同じつづりのためです。

ひとつの NotificationCenter インスタンスに通知名とオブザーバーを登録します。
通知を受けた NotificationCenter インスタンスは一致する通知名があれば各オブザーバーの処理を呼び出します。このしくみで通知を放送しているかのような機能を実現しています。

★用語【タイププロパティ】Type Property
型のプロパティです。つまりインスタンスは必要としません。
型名ドットタイププロパティ をどのコードのどこでも使えます。
対語【インスタンスプロパティ】Instance Property
インスタンス別に持つプロパティ。常にインスタンスを指定して使います。インスタンスごとに別の値を設定できる、インスタンスの状態などを表す変数です。

システムで決まっている通知名

NSNotification.Nameドキュメントに一覧があります。
どうもXcode10.2.1のSwiftではうまくアクセスできないものもあるようなのでObjective-Cのドキュメントを参照してください。(このページでSwift言語に切り替えも可能です)
https://developer.apple.com/documentation/foundation/nsnotificationname?language=objc

オブザーバーの登録

オブザーバーの登録には二つのメソッドがあります。
どちらも通知名(識別子)とオブザーバーが通知を受け取りたいポスターのオブジェクトの指定は共通です。
ポスターオブジェクトの指定は、特定のオブジェクトが発進した通知だけを受け取りたい場合に指定します。

addObserver(forName:object:queue:using:)
こちらは通知に対応するブロック(クロージャ)を登録します。比較的新しい(iOS4以降)書き方です。

// NotificationCenter クラスのオブザーバー登録メソッド(その1)宣言
func addObserver(forName name: NSNotification.Name?, 
         object obj: Any?, 
         queue: OperationQueue?, 
         using block: @escaping (Notification) -> Void) -> NSObjectProtocol

最後のブロックが見慣れないと思います。実際の呼び出し方もちょっと変わっています、積極的に使って慣れてくたださい。

最初の引数は通知の識別子です。通知の識別子は NSNotification.Name 型と決められています。
nilを渡すと識別子にかかわらず NotificationCenter が扱うすべての通知のオブザーバーとなります。
二つ目の引数はポスターのオブジェクト指定です。
nilを渡すとどのポスターからの通知も受け取ります
三つ目の引数は OperationQueue の指定です。ブロックをどのように実行するか指定します。(OperationQueue.main を指定するのを基本と考え必要により変更してください)
最後は通知に対応して実行するブロック(クロージャ)です。
最後がクロージャなので、トレイリングクロージャの書き方が一般的です。
引数が4つで最後がクロージャの場合、引数が三つのメソッドとして閉じ括弧を書きその直後にクロージャを書くのがトレイリングクロージャです。
addObserver(forName:object:queue:){ notification in
   // ここにクロージャの中身を書く
}

関数値:addObserver(forName:object:queue:using:) の関数値はオブザーバーの登録解除に使います。
詳しくは addObserver(forName:object:queue:using:) のドキュメントの例を確認してください。

ブロック(クロージャ)とは『関数に似ているコードの塊です』。
ブロックはクロージャに相当する Objective-C の機能です。

クロージャも引数と返り値を持てます。
(Notification) -> Void は Notification型の引数が一つで返り値はなしを意味します。

Notification型は後で説明する NSNotification クラスに対応するSwiftの型です。クロージャでは Notification型を使います。
// ダイナミックタイプの通知のオブザーバーにクロージャを指定する例
// .contentSizeDidChangeNotificationは別途定義必要(Xcode10.2.1専用)
observer = NotificationCenter.default.addObserver(
   forName: .contentSizeDidChangeNotification,
   object: nil,
   queue: OperationQueue.main
) { notification in
   print("notification=\(notification)")
}

クロージャでは引数とクロージャのコードはキーワード「in」で区切ります。
この例では引数の定数名を「notification」にしています。この定数名はクロージャ内で利用できます。

addObserver(_:selector:name:object:)
こちらはオブザーバーと通知に対応するメソッドのセレクタを登録します。iOS 2から使えた方法です。

// NotificationCenter クラスのオブザーバー登録メソッド(その2)宣言
func addObserver(_ observer: Any, 
         selector aSelector: Selector, 
         name aName: NSNotification.Name?, 
         object anObject: Any?)

ここから先は

7,206字 / 2画像 / 1ファイル
この記事のみ ¥ 500

今後も記事を増やすつもりです。 サポートしていただけると大変はげみになります。