LoginSignup
27
23

More than 5 years have passed since last update.

NotifwiftでNSNotification.userInfoをSwiftyに扱おう

Last updated at Posted at 2016-02-07

(追記: 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"
27
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
23