Search code examples
swiftnsnotificationcenter

Why can't I pass a dictionary with an enum key in a Notification


I tried passing a dictionary with an enum as key in a Notification ([TestEnum: String]). Unfortunately, type-casting the dictionary to [TestEnum: String] fails after receiving the notification.

enum TestEnum {
    case test
}

class NotificationTest {

    var sender: String = "" {
        didSet {
            NotificationCenter.default.post(name: Notification.Name(rawValue: "Test"), object: nil, userInfo: [TestEnum.test: "test"])
        }
    }

    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(notificationReceived(_:)), name: Notification.Name(rawValue: "Test"), object: nil)
    }

    @objc func notificationReceived(_ notification: Notification) {
        print("Notification Received")
        guard let userInfo = notification.userInfo as? [TestEnum: String] else { return } // Type casting fails here even though userInfo shows a TestEnum key in debugger
        print(userInfo[.test])
    }

}

let test = NotificationTest()
test.sender = "blah"

However, if i use rawValue of TestEnum as key, it works fine when notification.userInfo is casted to [String: String].


Solution

  • Just had a look at the source code of AnyHashable, when you cast a Hashable (your enum) to AnyHashable, it is wrapped in the property base within AnyHashable. Therefore, it cannot be directly cast back to your enum. Here uses reduce to transform [AnyHashable:Any] to [TestEnum:String]:

    @objc func notificationReceived(_ notification: Notification) {
        print("Notification Received")
        guard let userInfo = notification.userInfo?.reduce(into: [TestEnum:String](), { (result, entry) in
            if let key = entry.key.base as? TestEnum
            {
                result[key] = entry.value as? String
            }
        }) else {
            print(notification.userInfo)
            return
        } // Type casting fails here even though userInfo shows a TestEnum key in debugger
        print(userInfo[.test])
    }
    

    And because AnyHashable conforms to CustomStringConvertible, it can be cast to String directly.