---
title: "Observing and broadcasting"
slug: observing-and-broadcasting
section: tech
date: 2018-04-29T14:32:00.000Z
canonical: https://roland.leth.ro/blog/tech/observing-and-broadcasting
---

The usual solution to observe and broadcast is to use `NotificationCenter`:

```swift
final class Post { // 1

   var title: String
   var body: String

   init(title: String, body: String) {
      self.title = title
      self.body = body
   }

}

extension Notification.Name { // 2

   static let LTHPostReceived = Notification.Name(rawValue: "com.rolandleth.com.postReceivedNotification")

}

final class PostCreationController: UIViewController { // 3

   private let post: Post

   // [...]

   private func savePost() { // 4
      // [...]

      let userInfo = ["post": post] // 5
      let notification = Notification(name: .LTHPostReceived, object: nil, userInfo: userInfo) // 6

      NotificationCenter.default.post(notification) // 7
   }

   // [...]

}

final class FeedViewController: UIViewController { // 8

   // [...]

   override func viewDidLoad() {
      super.viewDidLoad()

      NotificationCenter.default.addObserver(self, // 9
                                             selector: #selector(postReceived),
                                             name: .LTHPostReceived,
                                             object: nil)
   }

   @objc
   private func postReceived(from notification: Notification) { // 10
      guard let post = notification.userInfo?["post"] as? Post else { return } // 11

      // Do something with post.
   }

   // [...]

}

```

Let's use a `Post` (1) as an example.

First of all, we need a `Notification.Name` extension (2) to create a custom notification name to pass it around.

Next, let's imagine a controller where we create a new post (3): in its save method (4), we have to create a `userInfo` dictionary (5), a `Notification` (6) and broadcast it (7).

Finally, let's imagine a controller to display a feed of posts (8): we need to add ourselves as an observer somewhere (9) and handle the notification when we receive it (10). The biggest downside here is that we need to try and extract our `Post` from the `userInfo` dictionary, found under the `post` key (which is a plain string, leaving room for errors), and only then can we use it.

A lot of boilerplate code, not quite safe and not quite pretty to use. I'm sure we can do better, don't you think? Let's start with a broadcaster:

```swift
final class GlobalBroadcaster {

   private var listenersTable: NSHashTable<AnyObject> = .weakObjects() // 1


   // MARK: - Adding listeners

   func addListener(_ object: AnyObject) { // 2
      listenersTable.add(object)
   }


   // MARK: - Helpers

   private func filteredListeners<T>() -> [T] { // 3
      return listenersTable.allObjects.compactMap { $0 as? T }
   }

   private func keyboardChanged(with notification: Notification) {
      // Some keyboard handling logic.
   }

   private func setKeyboardObserver() { // 4
      NotificationCenter.default.addObserver(forName: .UIKeyboardWillShow, object: nil, queue: nil) { [weak self] notification in
         self?.keyboardChanged(with: notification)
      }

      NotificationCenter.default.addObserver(forName: .UIKeyboardWillHide, object: nil, queue: nil) { [weak self] notification in
         self?.keyboardChanged(with: notification)
      }

   }


   // MARK: - Init

   init() {
      setKeyboardObserver()
   }

   // static let shared = GlobalBroadcaster() // 5

}

let Broadcaster = GlobalBroadcaster() // 6
```

The backbone of our broadcaster is the array of listeners (1), backed by an `NSHashTable<AnyObject>weakObjects()`. An `NSHashTable` is a collection similar to a `Set` — we want the objects inside it to be unique — and the `.weakObjects` initializer means the `NSHashTable` will store weak references to its contents and no retain cycles will occur—objects will be deallocated properly, instead of being kept alive indefinitely.

Next we need a method to add listeners (2), instead of exposing the `listenersTable` property. When we broadcast something, we will be interested in only one type of listeners, so (3) is a helper to filter only what we need — we'll see in just a bit how this plays out. This approach still lets us use usual `NotificationCenter` actors (4), but gives us a chance to parse or manipulate objects before exposing them to our app.

Finally, we'll be creating a global variable, so our `Broadcaster` can be available everywhere (6); or we can use a `static` property on `GlobalBroadcaster` (5), in which case the class itself could be named `Broadcaster` — I just like to type a bit less.

Next up, listeners. How do we listen and broadcast events? With protocols:

```swift
protocol PostCreationListener {

   func handlePostCreationBroadcast(with post: Post)

}

// Just an example.
protocol LoginListener {

   func handleUserLoginBroadcast(with user: User)
   func handleUserLogoutBroadcast()

}

final class FeedController: UIViewController {

   // [...]

   override func viewDidLoad() {
      super.viewDidLoad()

      Broadcaster.addListener(self) // 1
   }

   // [...]

}

extension FeedController: PostCreationListener { // 2

   func handlePostCreationBroadcast(with post: Post) { // 3
      // Do something with post.
   }

}
```

We conform `FeedController` to `PostCreationListener` (2), add ourselves as a listener (1) and implement the required method (3) — we'll be able to directly use our `post`, without `String` keys and casting.

Finally, we also need to broadcast events, right?

```swift
final class GlobalBroadcaster {

   // [...]

   func postCreated(_ post: Post) { // 1
      let listeners: [PostCreationListener] = filteredListeners() // 2

      listeners.forEach {
         $0.handlePostCreationBroadcast(with: post) // 3
      }
   }

   // [...]

}

final class PostCreationController: UIViewController {

   // [...]

   private func savePost() {
      Broadcaster.postCreated(post) // 4
   }

   // [...]

}
```

We'll add a new method on our `Broadcaster` (1) that uses our previously mentioned filter method: since we declare `listeners` (2) as `[PostCreationListener]`, the compiler can infer the `filteredListeners`' `T` return value. We then have to iterate through all listeners and call `handlePostCreationBroadcast:`. Lastly, `postCreated` will have to be called from our `PostCreationController` (4).

It might seem a bit more code, but we now have type-safety, an easy way to extend our listeners via protocols and a central place where we parse or manipulate objects before exposing them to our app.

Let me know what you think [@roland.leth.ro][1].

[1]:	https://bsky.app/profile/roland.leth.ro