iOSエンジニアのすーです!
7月に入り、かなり暑くなってきましたね。
「pokemon go」も日本で配信されてかなりホットな感じになっていますね...。
前回の記事とはちょっと変わってネタが小さくなりますが、Swiftの Enum でNSNotificationを扱いやすくするTipsを書こうと思います。
Enumを使ってNSNotificationで使うkeyを定義する
SwiftのEnumはObjective-Cのそれとは違って、要素がStringの Enum を作ることができるので
enum UserNotification: String { case DidLogIn case DidLogOut case DidReload }
こんな感じでわかりやすくまとめることができます。
Objective-Cの時は
// .h ファイル extern NSString * const UserDidLogInNotification; extern NSString * const UserDidLogOutNotification; // .m ファイル NSString * const UserDidLogInNotification = @"UserDidLogInNotification"; NSString * const UserDidLogOutNotification = @"UserDidLogOutNotification";
みたいな感じで定義していましたね...!
これを実際に使う時は、 Enum の rawValue を用います。
// observerの登録 NSNotificationCenter.DefaultCenter().addObserver( self, selector: #selector(self.userDidLogIn(_:)), name: UserNotification.DidLogIn.rawValue object: nil ) // 通知を送信 NSNotificationCenter.DefaultCenter().postNotificationName( UserNotification.DidLogIn.rawValue, object: user, userInfo: userInfo ) // keyを指定して解除する場合 NSNotificationCenter.defaultCenter().removeObserver( self, name: UserNotification.DidLogIn.rawValue )
...うーん。 確かに、 Enum でまとめるのは良いけれど、使い勝手がよくなったとはちょっと言えないですね。
そこで...
EnumにNSNotificationを扱いやすくする関数を追加する
Swiftの Enum には関数を定義することができるので、これを使って、先ほどの処理を包み込みます。
enum UserNotification: String { case DidLogIn case DidLogOut case DidReload func addObserver(observer: AnyObject, selector: Selector, object: AnyObject? = nil) { NSNotificationCenter.defaultCenter().addObserver(observer, selector: selector, name: self.rawValue, object: object) } func addObserver(object: AnyObject? = nil, queue: NSOperationQueue? = nil, usingBlock block: NSNotification -> Void) -> NSObjectProtocol { return NSNotificationCenter.defaultCenter().addObserverForName(self.rawValue, object: object, queue: queue, usingBlock: block) } func post(object: AnyObject? = nil, userInfo: [NSObject: AnyObject]? = nil) { NSNotificationCenter.defaultCenter().postNotificationName(self.rawValue, object: object, userInfo: userInfo) } func removeObserver(observer: AnyObject, object: AnyObject? = nil) { NSNotificationCenter.defaultCenter().removeObserver(observer, name: self.rawValue, object: object) } }
これで、
UserNotification.DidLogIn.addObserver(self, selector: #selector(self.userDidLogIn(_:))) UserNotification.DidLogIn.post(user, userInfo) UserNotification.DidLogIn.removeObserver(self)
といった形でスッキリ書くことができます...! 短いのは正義ですね
スッキリ書けるようにするために、一部objectやuserInfoに対して Default Value
を渡して、引数の省略ができるようにしています。
protocolとextensionを用いてより汎用的に
ただ、このままだと汎用性がなく、
enum UploadNotification: String { case WillStart case DidStart case Complete case Failed // ここにさっきの関数を追加するのはしんどい... }
みたいな別のNotificationを表すEnumが来た時に先ほどの処理をコピーして...ってなりかねないので、
protocol
とextension
を用いて 汎用的にしてみます。
protocol NSNotificationObservable: RawRepresentable { var rawValue: String { get } } extension NSNotificationObservable { func addObserver(observer: AnyObject, selector: Selector, object: AnyObject? = nil) { NSNotificationCenter.defaultCenter().addObserver(observer, selector: selector, name: self.rawValue, object: object) } func addObserver(object: AnyObject? = nil, queue: NSOperationQueue? = nil, usingBlock block: NSNotification -> Void) -> NSObjectProtocol { return NSNotificationCenter.defaultCenter().addObserverForName(self.rawValue, object: object, queue: queue, usingBlock: block) } func post(object: AnyObject? = nil, userInfo: [NSObject: AnyObject]? = nil) { NSNotificationCenter.defaultCenter().postNotificationName(self.rawValue, object: object, userInfo: userInfo) } func removeObserver(observer: AnyObject, object: AnyObject? = nil) { NSNotificationCenter.defaultCenter().removeObserver(observer, name: self.rawValue, object: object) } }
このように定義して、先ほどの Enum 達に、NSNotificationObservable
を適応してあげると、
// NSNotificationObservableを適応する extension UserNotification: NSNotificationObservable {} extension UploadNotification: NSNotificationObservable {} // どちらも同じように関数を呼び出すことができる! UserNotification.DidLogIn.addObserver(self, selector: #selector(self.userDidLogIn(_:))) UserNotification.DidLogIn.post(user, userInfo) UserNotification.DidLogIn.removeObserver(self) UploadNotification.Complete.addObserver(self, selector: #selector(self.uploadDidComplete(_:))) UploadNotification.Complete.post(user, userInfo) UploadNotification.Complete.removeObserver(self)
こんな感じで protocol
を適応するだけでサクッと使えるようになります!嬉しい。
protocolの定義の時点で、 RawRepresentable
を継承するように指定しているので、Enum以外にこのprotocolを適応することができないようになっています。
また、
protocol NSNotificationObservable { var rawValue: String { get } }
と宣言しているおかげで、間違って String型以外のEnumに適応してしまう...なんて事も防げます。 (Int型のEnumでは、rawValue は Int 型となってしまうので、衝突してしまいます。)
ちなみに
最初は、以下のような感じで NSNotificationCenter を拡張して使うことを考えていたのですが、結局あの長ったらしい記述から逃れられなくて諦めました。
こちらの方が普通の拡張だなあという感じですが。
public extension NSNotificationCenter { func addObserver<Key: RawRepresentable where Key.RawValue == String>(observer: AnyObject, selector aSelector: Selector, key: Key?, object anObject: AnyObject?) { self.addObserver(observer, selector: aSelector, name: key?.rawValue, object: anObject) } func postNotificationKey<Key: RawRepresentable where Key.RawValue == String>(key: Key, object anObject: AnyObject?) { self.postNotificationName(key.rawValue, object: anObject) } func postNotificationKey<Key: RawRepresentable where Key.RawValue == String>(key: Key, object anObject: AnyObject?, userInfo aUserInfo: [NSObject : AnyObject]?) { self.postNotificationName(key.rawValue, object: anObject, userInfo: aUserInfo) } func removeObserver<Key: RawRepresentable where Key.RawValue == String>(observer: AnyObject, key: Key?, object anObject: AnyObject?) { self.removeObserver(observer, name: key?.rawValue, object: anObject) } func addObserverForKey<Key: RawRepresentable where Key.RawValue == String>(key: Key?, object obj: AnyObject?, queue: NSOperationQueue?, usingBlock block: (NSNotification) -> Void) -> NSObjectProtocol { return self.addObserverForName(key?.rawValue, object: obj, queue: queue, usingBlock: block) } }
まとめ
NSNotificationを使った通知を使う時は、大体 NSNotificationCenter.defaultCenter()
から始まり、長い記述であれこれ書くのですが、
Enum を使ったアプローチで通知のkeyをまとめるのに加えて、addObserver/post/removeObserver
周りをスッキリさせてみました。
今回のコードはGistにもあげています。
積極採用中!!
子育て家族アプリFamm、カップル専用アプリPairyを運営する Timers inc. では、現在エンジニアを積極採用中! 急成長中のサービスの技術の話を少しでも聞いてみたい方、スタートアップで働きたい方など、是非お気軽にご連絡ください! 採用HP : http://timers-inc.com/engineerings