How do you pass data in a notification safely?
Predict first
What does this print?
let n = Notification(name: .init("x"), object: nil, userInfo: ["id": 1])
print(n.userInfo?["id"] as? Int as Any, n.userInfo?["id"] as? String as Any)
- Optional(1) nil
- Optional(1) Optional("1")
- nil nil
Mental model. An untyped userInfo is a parcel with a handwritten label. A typed wrapper is the printed form that says which fields exist.
A Notification carries a name, an optional object (usually the sender) and an optional userInfo dictionary of type [AnyHashable: Any]. That dictionary is untyped: every read is a cast that can fail silently. Wrap it: define the name and the keys once, and give posters and observers a small typed helper so the cast lives in one place.
Name your notifications with a namespace ("com.app.didLogin") so they cannot collide, and declare them as static let on Notification.Name. Modern code often prefers notifications(named:) (an AsyncSequence) or Combine’s publisher(for:), and recent SDKs add typed, concurrency-aware notification messages, so check what your deployment target offers.
The cast to Int works; the cast to String fails quietly with nil. Nothing warns you.
Common traps
- Casting the wrong type from
userInfo.as? Stringon anIntyieldsnil, and the bug is silent. - Stringly-typed names. A typo in the literal makes the observer never fire.
They might follow up with
- When is
objectbetter thanuserInfo? When the payload is the sender itself, such as the text field that changed. - Why not pass a whole model through
userInfo? It hides a data dependency. Prefer an explicit API for that.
Related questions
- 2.4.1 How does NotificationCenter deliver a notification, and on which thread?
- 2.4.4 NotificationCenter, delegate, closure or KVO: how do you choose?
← How do you avoid leaks and crashes with notification observers? NotificationCenter, delegate, closure or KVO: how do you choose? →