Skip to content

Instantly share code, notes, and snippets.

@ChrisLawther
Last active September 18, 2018 12:38
Show Gist options
  • Save ChrisLawther/b28dfb0b12dd0e3a8139a92b9a0243ec to your computer and use it in GitHub Desktop.
Save ChrisLawther/b28dfb0b12dd0e3a8139a92b9a0243ec to your computer and use it in GitHub Desktop.
// Custom Notifications, in a Swift-y way
// Firstly, to make testing much easier, let's write a protocol to describe what we want
// to be able to do with notifications:
protocol NotificationSource {
func post(_ notification: Notification)
func post(name: Notification.Name, object: Any?)
func post(name: Notification.Name, object: Any?, userInfo: [AnyHashable: Any]?)
func addObserver(forName name: NSNotification.Name?,
object obj: Any?,
queue: OperationQueue?,
using block: @escaping (Notification) -> Swift.Void) -> NSObjectProtocol
func removeObserver(_ observer: Any)
}
// Looks familiar? That's because it describes what NotificationCenter already does, so
// we can trivially opt NotificationCenter into conformance and in the future use a mock,
// an alternative or a default NotificationCenter interchangeably:
extension NotificationCenter: NotificationSource { }
// Now, the first thing we need to be able to do is post a notification. Lets make use of
// generics and another protocol to make that trivial at the call site:
protocol Postable {
static var name: Notification.Name { get }
}
extension NotificationSource {
func post<T: Postable>(_ thing: T) {
post(name: T.name, object: thing)
}
}
// So now we can post anything that conforms to `Postable`:
struct SomeMessage {
let payload: String
}
extension SomeMessage: Postable {
static let name = Notification.Name("SomeMessageDidHappen")
}
// Next, we need to be able to observe these. Borrowing from talk.objc.io, we'll
// wrap the token NotificationCenter returns in a self de-registering class:
// (see https://talk.objc.io/episodes/S01E27-typed-notifications-part-1)
class Token {
let token: NSObjectProtocol
let center: NotificationSource
init(token: NSObjectProtocol, center: NotificationSource) {
self.token = token
self.center = center
}
deinit {
center.removeObserver(token)
}
}
// The registration then becomes:
extension NotificationSource {
func addObserver<N: Postable>(using block: @escaping (N) -> ()) -> Token {
return Token(token: addObserver(forName: N.name, object: nil, queue: nil, using: { note in
block(note.object as! N)
}), center: self)
}
}
// And usage is then as simple as:
// Arrange to handle the arrival of our `SomeMessage` notification
var token:Token? = NotificationCenter.default.addObserver { (msg:SomeMessage) in
print("Received notification saying: \(msg.payload)")
}
// Create one to send
let message = SomeMessage("Hello world")
// Send it. The block above will report its arrival
NotificationCenter.default.post(message)
// Deregister our interest
token = nil
// Send it again. Nothing will receive it
NotificationCenter.default.post(message)
// NOTE: This isn't applicable for receiving standard system notifications as they
// use the `userInfo` property of the `Notification` and we can't immediately/automatically
// decode them. See the talk.objc.io video linked above for more on that topic.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment