367 Views
November 28, 15
スライド概要
第64回 Cocoa 勉強会関西でお話した NSNotificationCenter を Swift で使ってみた時の印象と、そこから Swift に似合う Notification のあり方について考察してみたスライドです。
※ Docswell での公開に移行する直前の Slideshare での閲覧数は 4,157 でした。
正統派趣味人プログラマー。プログラミングとは幼馴染です。
NSNotification in Swift 2015.11.28 @ 第64回 Cocoa 勉強会関⻄ EZ-NET 熊⾕友宏 http://ez-net.jp/
熊谷友宏 EZ-NET http://ez-net.jp/ @es̲kumagai iOS, OS X, Apple Watch アプリ CodePiece いつもの電卓 ソースコードを Twitter と Gist に同時投稿できる。 計算式も見える電卓アプリ。 watchOS 1 対応 音で再配達ゴッド EZ-NET IP Phone 簡単操作で 再配達の申し込み。 iPhone でひかり電話を使う。 自宅 LAN からの利用専用
CodePiece for OS X 勉強会を楽しむアプリ ソースコードを Twitter と Gist に同時投稿できる 勉強会で知見をみんなと共有したい時とかに便利! できること ✓ コードを添えて呟ける ✓ 普通の呟きもできる ✓ ハッシュタグを 付け忘れずに呟ける https://github.com/EZ-NET/CodePiece (Pre-Release)
熊谷友宏 EZ-NET http://ez-net.jp/ @es̲kumagai 勉強会 横浜 iPhone 開発者勉強会 カジュアル Swift 勉強会 【 横浜・馬車道 】 #yidev 【 横浜・青葉台 】 #cswift わいわい・ゆるく、iPhone 開発者の みんなで楽しく過ごすのが目的の会 ゆるくみんなで Swift を語らえる場を 作りたくて始めた会 第21回を 2015-12-12 に開催 第4回を 2015-12-26 に開催
熊谷友宏 EZ-NET http://ez-net.jp/ @es̲kumagai 書籍 / 登壇 Xcode 5 徹底解説 MOSA Xcode 5 の全機能を 徹底的に解説した本 OSX/iOS 系の歴史深い 有料会員制の勉強会 Xcode 7 でも役立つはず 法人会員も多数 紙版は絶版、電子書籍は販売中 秀和システム 10x-Eng.com で取扱予定
NSNotification in Swift
NSNotification 概要 ▶ 何かが起こったことを 通知 で伝える ▶ 通知は 通知センター が管理 ▶ 知りたい通知を通知センターに登録 ▶ 通知があると知らせてくれる
通知センター NSNotificationCenter POST Object 通知センター OBSERVE NSNotificationCenter - addObserver: selector: name: object: - postNotification: 通知を送信 通知を伝達 伝達を要請 Object Object 通知ハンドラー Object 通知ハンドラー 伝達を要請 A. B. C. 伝達を要請 通知を送信 通知を伝達
通知 NSNotification ▶ 通知は 名前 で区別される ▶ 通知元の オブジェクト が添えられる ▶ 任意の 追加情報 が付与されることも 通知 NSNotification name: String object: AnyObject? userInfo: [NSObject : AnyObject]?
通知 用意されている通知(ほんの1例) ▶ NSApplicationWillTerminateNotification アプリが終了するときに通知される ▶ ACAccountStoreDidChangeNotification アカウントストアが変更されたときに通知される ▶ AVAudioSessionRouteChangeNotification 音声の出力先が変化したときに通知される ▶ NSWorkspaceWillSleepNotification コンピューターがスリープするときに通知される ▶ kReachabilityChangedNotification ネットワーク環境が変化したときに通知される
通知センター メリット的な特徴 ▶ 突如発生する状況変化に対応しやすい ▶ 各オブジェクトが自主的に対応可能 ▶ 全体を把握して制御する負担を軽減 デメリット的な特徴 ▶ 他オブジェクトと足並みを揃えにくい ▶ 誰が通知を受け取るか分からない ▶ 通知の発生場所を掴みにくい
通知センター 使いどころ ▶ 状況変化を受動的に受けたいときに使う ▶ 自発的な状況確認で充分なら使わない ▶ 伝えたい相手が明確なときは使わない ▶ 継続的な任務の遂行に使うと効果的
通知センター スリープ状態へ移行を通知 具体例 NSApplication ビューの表示 スリープからの復帰を通知 起動完了を報告 アカウントの検証 SNS Controller 初期化を実行 NSAppDelegate SNS の利用準備 最新データを取得 自動更新の開始 View Controller 変更内容を処理 自動更新の再開 Timeline View Controller 最新データを取得 自動更新の中断 Tweet Text 内容の変更を報告 ボタンの有効化 Tweet Button メソッド デリゲート 通知
通知センターを使う
通知センターを使う ふたつの観点 A. OBSERVE … 通知の監視 B. POST … 通知の送信
OBSERVE 通知の監視
OBSERVE 準備 A. 通知を受け取るメソッドを準備 実施 1. 通知センターを取得 2. 通知の伝達を要請 始末 Z. 通知の伝達要請を解除
OBSERVE A. 通知を受け取るメソッドを準備 ▶ インスタンスメソッドで実装する ▶ 引数で NSNotification を受け取る func sampleNotificationReceived( notification: NSNotification) { // ここに通知を受け取ったときの処理を書く }
OBSERVE 1. 通知センターを取得 ▶ NSNotificationCenter のメソッドで取得 ▶ シングルトンなインスタンス let notificationCenter = NSNotificationCenter.defaultCenter()
OBSERVE 2. 通知の伝達を要請 ▶ NSNotificationCenter に対して要請 ▶ 伝達先のインスタンスとセレクタを渡す notificationCenter.addObserver( self, selector: "sampleNotificationReceived:", name: ESSampleNotification, object: nil) たとえば viewWillAppear で実行
OBSERVE Z. 通知の伝達要請を解除 ▶ NSNotificationCenter から登録解除 ▶ 適切なタイミングで必ず実施すること notificationCenter.removeObserver( self, name: ESSampleNotification, object: nil) たとえば viewWillDisappear で実行
通知を監視する まとめ A Observer NSNotificationCenter ESSampleNotification addObserver:selector:name:object: sampleNotificationReceived: sampleNotificationReceived: ESSampleNotification ESSampleNotification removeObserver:name:object: ESSampleNotification
POST 通知の送信
POST 準備 A. 通知の仕方を決める 実施 1. 通知センターを取得 2. 通知を送信
POST A. 通知の仕方を決める ▶ 通知名を文字列で決める ▶ object で扱うものを決める 一般に “通知を送信したオブジェクト” ▶ userInfo で扱うものを決める 完全に自由形式 通知 NSNotification name: String object: AnyObject? userInfo: [NSObject : AnyObject]?
POST 1. 通知センターを取得 ▶ NSNotificationCenter のメソッドで取得 ▶ シングルトンなインスタンス let notificationCenter = NSNotificationCenter.defaultCenter()
POST 2. 通知の送信 ▶ NSNotification を作る ▶ 通知センターに送信する let notification = NSNotification( name: ESSampleNotification, object: self, userInfo: nil) notificationCenter .postNotification(notification)
通知を送信する まとめ An Object postNotification: ESSampleNotification An Observer NSNotificationCenter sampleNotificationReceived: ESSampleNotification An Observer sampleNotificationReceived: ESSampleNotification
気になったところ in Swift
気になったところ (1/5) 通知がどんな情報を持っているか判らない ▶ 通知名は String 型 → できれば型で扱いたい ▶ object は AnyObject? 型 → 具体的な型で扱いたい ▶ userInfo は [NSObject : AnyObject]? 型 → 型が不明瞭な上に自由度が高すぎる 通知 NSNotification name: String object: AnyObject? userInfo: [NSObject : AnyObject]?
気になったところ (2/5) 通知を受けるメソッドの作成が必要 ▶ クロージャーを使いたい ▶ セレクターは使いたくない notificationCenter.addObserver( self, selector: "sampleNotificationReceived:", name: ESSampleNotification, object: nil)
気になったところ (3/5) 通知センターを都度取得する必要がある ▶ 手続き的な事前準備をしたくない ▶ コードをもっとスリムに書きたい let notificationCenter = NSNotificationCenter.defaultCenter()
気になったところ (4/5) 通知センター主体のコーディングになる ▶ 通知センターを意識しないで書きたい ▶ Notification と Observer に着目したい notificationCenter.addObserver( self, selector: "sampleNotificationReceived:", name: ESSampleNotification, object: nil) notificationCenter .postNotification(notification)
気になったところ (5/5) 監視の解除を手動で実施しないといけない ▶ 後始末が必要なのは原始的? ▶ 監視解除を書くのに煩わされたくない notificationCenter.removeObserver( self, name: ESSampleNotification, object: nil)
これらを解消すると どんなコードになるんだろう…
ESNotification in Swift https://github.com/EZ-NET/ESNotification
ESNotification 構想 ▶ 通知は文字列ではなく型で区別 ▶ 通知センターを意識せずに使える ▶ 通知時の処理をクロージャーで書ける ▶ 監視解除が自動で行われる ▶ 既存の通知センターと互換 https://github.com/EZ-NET/ESNotification
ESNotification 大まかな構成 Notification An Notification Object post NSNotification class observeBy(̲:handler:) ̲notificationHandlers ̲rawNotificationObserver Notification Manager NSNotification Center
ESNotification を使う in Swift ESNotification
POST 通知の送信 ESNotification
POST 準備 A. 通知型を設計 従来 ▶ 通知の仕方を決める 実施 1. 通知を送信 ▶ 通知センターを取得 ▶ 通知を送信 ESNotification
POST A. 通知型を設計 ▶ 名前の間違いや衝突をコンパイラで回避可能 ▶ 扱う情報を明確に持てる class LocationChangedNotification : Notification { var x: Int var y: Int } ESNotification
POST 1. 通知を送信 ▶ 通知センターの取得が不要 ▶ 通知を主体に考えられる LocationChangedNotification(x:10, y:3).post() ESNotification
OBSERVE 通知の監視 ESNotification
OBSERVE 準備 ✓ なし 従来 ▶ 通知を受け取るための メソッドを準備 実施 1. 目的の通知を監視 ▶ 通知センターを取得 ▶ 通知の伝達を要請 ▶ 通知の伝達要請を解除 ESNotification
OBSERVE 1. 目的の通知を監視 ▶ 通知時の処理をクロージャーで指定 ▶ 文字列によるセレクター指定が不要 LocationChangedNotification .observeBy(self) { notification in } ESNotification
RELEASE 監視の解除 ESNotification
RELEASE 監視の解除方針 ▶ NotificationManager は Observer を weak で管理 ▶ Observer が解放されると 関連する監視を解除 ▶ 明示的な監視解除も可能にする ESNotification
RELEASE 監視を明示的に解除する場合 ▶ 監視登録時にハンドルを取得しておく ▶ ハンドルに対して release を実行する let handle = LocationChangedNotification .observeBy(self) { notification in … } // 不要になったタイミングでハンドルをリリース handle.release() ESNotification
RELEASE 関連する監視を全て解除する場合 ▶ NotificationManager を使う ▶ release メソッドに observer を渡す NotificationManager.release(observer: self) ESNotification
通知センターとの互換性 compatibility ESNotification
通知センターとの互換性
通知型を通知センターで監視できる
▶ 通知名には notificationIdentifier を使う
▶ 受信した通知は object で渡される
notificationCenter.addObserver(
self,
selector: "locationChangedNotificationReceived:",
name: LocationChangedNotification.notificationIdentifier,
object: nil)
func locationChangedNotificationReceived(n: NSNotification) {
}
let notification
= n.object as! LocationChangedNotification
ESNotification
通知センターとの互換性 名前付き通知を監視できる ▶ NamedNotification を監視する ▶ 通知センターからの通知も取得可能 NamedNotification.observe( ESSampleLegacyNotification, by: self) { notification in } ESNotification
通知センターとの互換性 名前付き通知を送信できる ▶ NamedNotification で送信する ▶ NSNotification と同等の通知ができる NamedNotification(ESSampleNotification, object: self, userInfo: nil).post() ESNotification
ESNotification 使い方のまとめ 送信 LocationChangedNotification(x:10, y:3).post() 監視 LocationChangedNotification .observeBy(self) { notice in } print("Location:\(notice.x),\(notice.y)") ESNotification
もう少し手軽に使いたい ESNotification
Observer を 暗黙的に指定したい 🤔 ESNotification
Observer を暗黙指定したい 理由はここで self を指定することが多いから ▶ 既定の引数で self を指定する方法は? ▶ またはその代替案は? LocationChangedNotification .observeBy( self ) { notification in } … ESNotification
self を省略する手は なさそう … 🙁 ESNotification
Observer を 主体にしてみる ESNotification
Observer を主体にする A. Observable プロトコルを作成 ▶ 通知監視できることを意味するプロトコル ▶ ここに通知監視に関する機能を備える protocol NotificationObservable : AnyObject { } ESNotification
Observer を主体にする
B. Observable に機能を実装
▶ 通知を監視するための機能をつける
▶ 任意のタイミングで監視解除も可能にする
extension NotificationObservable {
func observeNotificationNamed(
name: String,
handler:(NamedNotification)->Void) -> HandlerID
func observeNotification<T:NotificationProtocol>(
handler: (T) -> Void) -> HandlerID
func releaseObservingNotifications()
}
ESNotification
Observer を主体にする C. 通知を監視可能にする ▶ NotificationObservable を適用する ▶ 通知監視に関する振る舞いが備わる import ESNotification extension ViewController : NotificationObservable { } ESNotification
Observer を主体にする 1. 通知を監視する ▶ 自身のメソッドを使って監視を開始 ▶ 引数で監視する通知を指定 self.observeNotification { (notification: LocationChangedNotification) in } ESNotification
Observer を主体にする 2. 通知監視を解除 ▶ 自身のメソッドを使って監視を解除 ▶ 自身が登録した監視すべてが解除される self.releaseObservingNotifications ESNotification
Observer を主体にする 使い方のまとめ 送信 LocationChangedNotification(x:10, y:3).post() 監視 self.observeNotification { (notice:LocationChangedNotification) in } print("Location:\(notice.x),\(notice.y)") ESNotification
Observer 主体だと 監視する通知を読み取りにくい 🤔 ESNotification
監視する通知を読み取りにくい 通知型の登場位置による差異 Notification 主体 LocationChangedNotification .observeBy(self) { notification in } 扱う通知が すぐに分かる Observer 主体 self.observeNotification { (notification: LocationChangedNotification ) in } ここまで読んで やっと分かる ESNotification
LocationChangedNotification.observeBy(self) { notification in } やっぱり Notification 主体で self を省略したい でも実現方法が見つからない … self.observeNotification { (notification: LocationChangedNotification) in } ESNotification
型の表記を 少し前に持ってきてみる ESNotification
型の表記を少し前に持ってくる
型情報を引数で指定
▶ 型情報を引数で渡す
▶ ただし引数の型情報は使わない
extension NotificationObservable {
func observeNotification<T:NotificationProtocol>(
handler: (T) -> Void) -> HandlerID
func observeNotification<T:NotificationProtocol>(
_ : T.Type,
handler: (T) -> Void) -> HandlerID
}
ESNotification
型の表記を少し前に持ってくる 視覚的な差異 型情報を引数にとらない場合 self.observeNotification { (notification: LocationChangedNotification ) in } ここでようやく 登場した通知型が 型情報を引数にとる場合 self.observeNotification( LocationChangedNotification.self ) { notification in } ほんの少しだけ 前に登場 ESNotification
型の表記を少し前に持ってくる
構文的な特徴
定義
func observeNotification<T:NotificationProtocol>(
_ : T.Type,
handler: (T) -> Void) -> HandlerID
}
使うとき
通知型が少し
前に登場
型推論で
省略できる
self.observeNotification(
LocationChangedNotification.self) { notification in
}
この self は
欲しくない …
ESNotification
こちらも self が カッコわるい … 🙁 ESNotification
良い方法ありませんか? スマートに self を省略したい ESNotification
使い方のまとめ in Swift ESNotification
ESNotification in Swift シンプルな使用例 // 通知を定義する final class SampleNotification : Notification { } class ViewController: NSViewController, NotificationObservable { override func viewDidLoad() { super.viewDidLoad() // 通知を監視する self.observeNotification { (notification: SampleNotification) in } } print("\(notification) received.") @IBAction func sendNotification(sender:AnyObject?) { } } // 通知を送信する SampleNotification().post() ESNotification
ESNotification in Objective-C ESNotification
ESNotification in Objective-C 必要な機能の検討 ▶ NSNotificationCenter を Objective-C で使うのは違和感がない ▶ ESNotification の通知型を Objective-C からも送信したい ESNotification
通知に関する機能追加 NSNotificationCenter を拡張 ▶ NSNotificationCenter に機能を追加 ▶ 型拡張で直接実装 extension NSNotificationCenter { func addObserver(observer: AnyObject, selector: Selector, ESNotification: NotificationProtocol.Type, object: AnyObject?) func removeObserver(observer: AnyObject, ESNotification: NotificationProtocol.Type, object: AnyObject?) func postESNotification(notification: Postable) } ESNotification
通知クラスを定義する
Swift または Objective-C での定義方法
▶ Notification プロトコルに準拠させる
▶ Objective-C 互換クラスとして定義
// Swift で定義する場合
class ESSampleNotification : NSObject, Notification {
}
// Objective-C で定義する場合
#import <ESNotification/ESNotification-Swift.h>
@interface ESSampleNotification : NSObject <ESNotification>
@end
ESNotification
通知を送信する in Objective-C ▶ 通知センターを使って送信 ▶ 従来に近い送信方法(使うメソッドは異なる) NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter]; ESObjCNotification* notification = [[ESObjCNotification alloc] init]; [notificationCenter postESNotification:notification]; ESNotification
通知を監視する in Objective-C ▶ 監視対象を通知型で指定 ▶ 従来に近い送信方法(使うメソッドは異なる) NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(objcNotificationReceived:) ESNotification:[ESObjCNotification class] object:nil]; ESNotification
通知監視を解除する in Objective-C ▶ 慣例に倣い、通知解除を手動で実行 ▶ 解除対象を通知型で指定 NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter removeObserver:self ESNotification:[ESObjCNotification class] object:nil]; ESNotification
使い方のまとめ in Objective-C ESNotification
ESNotification in Objective-C
シンプルな送信例
#import <ESNotification/ESNotification-Swift.h>
// 通知を定義する
@interface ESSampleNotification : NSObject <ESNotification>
@end
@implementation ESSampleNotification
@end
// 通知を使う
@implementation ESViewController
- (void)sendNotificationAction:(id)sender
{
// 通知を送信する
NSNotificationCenter* notificationCenter
= [NSNotificationCenter defaultCenter];
ESSampleNotification* notification
= [[ESSampleNotification alloc] init];
}
[notificationCenter postESNotification:notification];
@end
ESNotification
ESNotification in Objective-C
シンプルな監視例
#import <ESNotification/ESNotification-Swift.h>
@implementation ESViewController
- (void)objcNotificationReceived:(NSNotification*)notification
{
ESSampleNotification* notice = notification.object;
}
- (void)viewWillAppear
{
[super viewWillAppear];
NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];
}
[notificationCenter addObserver:self
selector:@selector(objcNotificationReceived:)
ESNotification:[ESSampleNotification class]
object:nil];
}
- (void)viewWillDisappear
{
[super viewWillDisappear];
NSNotificationCenter* notificationCenter = [NSNotificationCenter defaultCenter];
}
[notificationCenter removeObserver:self
ESNotification:[ESSampleNotification class]
object:nil];
@end
ESNotification
ESNotification in Swift & Objective-C https://github.com/EZ-NET/ESNotification
NSNotification in Swift まとめ ▶ NSNotification 概要 ▶ もっと気軽に使いたい ✓ ESNotification in Swift ✓ Swift みたいに気軽に使えるように ▶ Objective-C でも使いたい ✓ ESNotification in Objective-C ✓ 従来と同じ感覚で使えるように https://github.com/EZ-NET/ESNotification