(追記: potatotips27でNotifwiftについて発表しました。スライドはこちら)
https://speakerdeck.com/takasek/nsnotification-dot-userinfowo-swiftynixi-ou
NSNotificationCenter。使いにくくありませんか?
NSNotificationCenterは、そこかしこで微妙に面倒な取り扱いを強いられます。
observerの管理が面倒
func addObserver(observer: AnyObject, selector aSelector: Selector, name aName: String?, object anObject: AnyObject?)
を使うと、selectorをリテラルで指定しなければなりません。コンパイラでselectorの存在を保証してくれないのは嫌ですね。
func addObserverForName(name: String?, object obj: AnyObject?, queue: NSOperationQueue?, usingBlock block: (NSNotification) -> Void) -> NSObjectProtocol
を使えばナウいblockでの実装が可能ですが、removeObserverのためには戻り値をどこかに保持しておく必要があります。えー? そのためにArrayを作れって…?
そしてどちらにせよ、observeを止めるためにはremoveObserverを明示的に呼ぶ必要があります。removeし忘れて煮え湯を飲まされた苦い思い出、誰にでもありますよね。
userInfoが使いにくい
Notificationの付帯情報として指定できるuserInfo、型はなんと [String: AnyObject]
。keyの整合性に気をつけないといけないわ、StructやEnumを渡せないわ、定義を見てるだけで悲しくなります。
Objective-Cとの互換性のため仕方ないこととは言え、Swiftで書くならもっと静的型付けを活用したいものですよね。
記述が長い
NSNotificationCenter.defaultCenter().postNotificationName(SomeNotificationName, object:nil, userInfo: ["hogehoge": hoge])
NSNotificationCenter.defaultCenter().addObserver(self, selector: "someNotificationReceived:", name: SomeNotificationName, object: self)
…どんだけ長ったらしく書かせるんだ!
一文字タイプするごとにHPの削られる音が聞こえます。
というわけで、ライブラリ作りました。
使い方(例)
let didReceiveUserNotification = "didReceiveUserNotification"
final class MyViewController: UIViewController {
@IBOutlet var userInfoView: MyUserInfoView!
let notifwift = Notifwift()
override func viewDidLoad() {
super.viewDidLoad()
notifwift.observe(didReceiveUserNotification, block: reload)
}
private func reload(user: User) {
userInfoView.reload(user)
}
}
final class MyUserRepository {
func fetchUser(id: Int) {
MyAPIManager.fetchUser(id) { (user: User) in
Notifwift.post(didReceiveUserNotification, payload: user)
}
}
}
Notifwift()
でインスタンスを生成すると、そのインスタンスが生きている間だけNSNotificationをobserveします。
もうremoveObserver忘れに悩まされる心配はありません。
NSNotificationのpost時には、payloadとしてUserをダイレクトに渡せます。クロージャで受けてもいいですし、Userを引数で受けるメソッドがあればそのままレシーバとして指定できますね。
詳細な使い方
do {
let nt = Notifwift()
nt.observe(notificationName) { notification in
print("Notifwift can observe NSNotification in simple way.", notification)
}
Notifwift.post(notificationName)
//printed:
// Notifwift can observe NSNotification in simple way. NSConcreteNotification 0x7fdfa0414b20 {name = Hoge}
}
Notifwift.post(notificationName)
//printed nothing. Observers expire when the Notifwift instance(in this case) is destructed.
Notifwiftの内部でuserInfoをラップ/アンラップされるので、ユーザーは型にとらわれない payload
を受け渡しに利用できます。payloadはジェネリクス型をサポート。
let nt = Notifwift()
nt.observe(notificationName) { (payload:String) in
print("This closure observes nothing but NSNotification with String payload.", payload)
}
nt.observe(notificationName) { (payload:Int) in
print("This closure observes nothing but NSNotification with Int payload.", payload)
}
Notifwift.post(notificationName, payload:"aaaa")
//printed:
// This closure observes nothing but NSNotification with String payload. aaaa
Notifwift.post(notificationName, payload:1)
//printed:
// This closure observes nothing but NSNotification with Int payload. 1
もちろんサブタイプにも対応。
class Animal {}
class Cat: Animal {}
let nt = Notifwift()
nt.observe(notificationName) { (p:Animal) in
print("Received Animal.", p)
}
nt.observe(notificationName) { (p:Cat) in
print("Received Cat. Yes, of course, Notifwift recognizes subtypes.", p)
}
Notifwift.post(notificationName, payload:Animal())
//printed:
// Received Animal. (Animal #1)
Notifwift.post(notificationName, payload:Cat())
//printed:
// Received Animal. (Cat #1)
// Received Cat. Yes, of course, Notifwift recognizes subtypes. (Cat #1)
値付きEnumとの相性は抜群!
enum SomeResult {
case Success(String)
case Fail(NSError)
}
let nt = Notifwift()
nt.observe(notificationName) { (p:SomeResult) in
switch p {
case .Success(let str):
print("Any Type can be used as a payload", str)
case .Fail(let err) where err.code == 403:
print("not authorized")
case .Fail(let err) where err.code == 404:
print("not found")
case .Fail(let err):
print("Notifwift has a chemistry with Enum Associated Values.", err)
}
}
Notifwift.post(notificationName, payload:SomeResult.Success("like this."))
//printed:
// Any Type can be used as a payload like this.
Notifwift.post(notificationName, payload:SomeResult.Fail(NSError(domain: "", code: 0, userInfo: nil)))
//printed:
// Notifwift has a chemistry with Enum Associated Values. Error Domain= Code=0 "(null)"
NSNotificationのobject, queueもそのまま使えます。
let obj1 = NSObject()
let obj2 = NSObject()
let nt = Notifwift()
nt.observe(notificationName) { _ in
print("Received from all objects")
}
nt.observe(notificationName, from: obj1) { _ in
print("Received from obj1 only")
}
nt.observe(notificationName, from: obj2) { _ in
print("Received from obj2 only")
}
Notifwift.post(notificationName, from: obj1)
//printed:
// Received from all objects
// Received from obj1 only
Notifwift.post(notificationName, from: obj2)
//printed:
// Received from all objects
// Received from obj2 only
インストール方法
Cocoapods, Carthageに対応してます。(2015/2/12, Carthageで問題なく動くこと確認できました。 @su_k ありがとうございました!)
pod "Notifwift"
github "takasek/Notifwift"