Swift3のNotificationCenterを今までと近い形で使ってみる

  • 41
    いいね
  • 3
    コメント

(追記) 正しい使い方は @mono0926 さんがこちらにて書いていますのでそちらへどうぞ :runner:

回れ右。

※以下の内容は一応アーカイブとして残しておきますが、良き使い方ではないのでご了承くださいませ。




Swift3.0で変わったNSNotificationCenter

Swift2からSwift3に変わって、Foundation系にも手が加えられ、 NSNotificationCenter 周りにも変更がありました。

Swift2.2とSwift3.0でaddObserveする場合の同じ処理を見比べてみます。

Swift2
class Hoge {
    @objc func doSomething(notification: Notification) {
        // ...
    }
}

NSNotificationCenter.defaultCenter().addObserver(
    self, 
    selector: #selector(hoge.doSomething(_:)), 
    name: "NotificationKey", 
    object: nil
)

Swift3
class Hoge {
    @objc func doSomething(_ notification: Notification) {
        // ...
    }
}

let hoge = Hoge()
NotificationCenter.default.addObserver(
    hoge, 
    selector: #selector(hoge.doSomething(_:)), 
    name: NSNotification.Name("NotificationKey"),  //おや...?
    object: nil
)

ここで、 NSNotification.Name という見慣れないものがでてきました。
(ちなみに Notification.Name ものもあって、辿ると NSNotification.Name のエイリアスになっているのでほぼ同義です。どちらで書いても間違いではなさそう。)

どうやらSwift3からは通知を発行するためのnameStringではなくてこのNSNotification.Nameを使えとなったようです。
しかし、これ毎回

NSNotification.Name("SomeNotificationKey")

ってやるの しんどい ですよね。
なので、今までどおりの形に近い感じで使えるようにしてみます。


魔法をかける

できれば今までどおり name: "NotificationKey" と書きたいので、 NSNotification.Name に魔法をかけます。
ここで、 ExpressibleByStringLiteral というprotocolの出番となります。
ちなみに、この ExpressibleByStringLiteral は、Swift2では StringLiteralConvertible という名前でした。

ExpressibleByStringLiteralをNotification.Nameに適応させる
extension NSNotification.Name: ExpressibleByStringLiteral {
    public init(unicodeScalarLiteral value: String) {
        self.init(rawValue: value)
    }

    public init(extendedGraphemeClusterLiteral value: String) {
        self.init(rawValue: value)
    }

    public init(stringLiteral value: String) {
        self.init(rawValue: value)
    }
}

こうすることで、

// good!
NotificationCenter.default.addObserver(
    hoge, 
    selector: #selector(hoge.doSomething(_:)), 
    name: "NotificationKey", 
    object: nil
)

このように、文字リテラル("")を使って今までどおりの書き方ができるようになります。

...え、これが 変数の場合はどうするの? ってなりますよね。
その場合は、変数を定義する型を Notification.Name にします。


class Hoge {
    // 左辺側で型を指定してあげることで、右辺はStringではなくて、Notification.Nameと推論される
    static let NotificationKey: NSNotification.Name = "NotificationKey"

    // NG : 今までどおりの宣言だと、Stringと推論される
    // static let NotificationKey = "NotificationKey"

    @objc func doSomething(_ notification: Notification) {
        // ...
    }
}

// good!
NotificationCenter.default.addObserver(
    hoge, 
    selector: #selector(hoge.doSomething(_:)), 
    name: Hoge.NotificationKey,  // 今までどおりいける!
    object: nil
)

まとめ

Swift3になって NSNotification.Name とか出てきてちょっと怯んだのですが、少し工夫してあげると扱いやすくなるかもしれません。 ExpressibleByStringLiteral ありがとう。

おまけ

そこそこ利用頻度の高い、UIApplicationDidBecomeActiveNotification といったUIApplication周りのNotificationのkeyに関しては、

// swift2まではこれ
UIApplicationDidBecomeActiveNotification
// swift3からはこれ
NSNotification.Name.UIApplicationDidBecomeActive

のように変わったので気をつけましょう。もしかしたら自動マイグレーションなり補完が効くかもしれませんが。。。
(最初どこに行ってしまったのか気づけなかった...)

(※注 Xcode8 beta6 時点のSwift3.0を基に書いています。