iOS NotificationCenter in a better way

iOS NotificationCenter in a better way

A common pattern in app development is notifications (NSNotification and NotificationCenter). With notifications you can broadcast out messages to multiple listeners. This can be useful in many cases. A common example is if you have many UIViewControllers that all need to update on some model change.

However, I feel that the standard way of using NotificationCenter is error prone, complicated and verbose. I mostly use another approach, where you create your own version of NotificationCenter.

The normal way

First, an example on normal use of NotificationCenter:

extension Notification.Name { 
    static let didUpdateCar = Notification.Name("didUpdateCar")
    static let didReceiveData = Notification.Name("didReceiveData")
}
 
class MyViewController: UIViewController {
 
    override func viewDidLoad(_ animated: Bool) {
        super.viewDidLoad(animated)
        setupNotifications()
    }
 
    private func setupNotifications() {
        NotificationCenter.default.addObserver(self, selector: #selector(didUpdateCar(_:)), name: .didUpdateCar, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(didReceiveData(_:)), name: .didReceiveData, object: nil)
        // ... a long list here
    }
 
    deinit {
        NotificationCEnter.default.removeObserver(self)
    }
 
    @objc private func didReceiveData(_ notification: NSNotification) {   
        // This silently does nothing if you change the userInfo on the senders side. A crash to prefer?
        if let data = notification.userInfo as? [String: Int] {
            for (name, ageInYears) in data {
                print("\(name) is \(ageInYears) years old.")
            }
        }
    }
}
 
class AnotherViewController: UIViewController {
    func didClickButton() {
        // Let's pray the receiver knows what we're sending
        let data = ["Jonas": 20, "Andrea": 3, "Oscar": 42]
        NotificationCenter.default.postNotification(name: .didReceiveData, object: data)
    }
}

Here we just use the standard APIs. We add an extension to Notification.Name for a bit smoother setup. But other that that, this is the default way to broadcast notifications. What I don’t like about this approach:

  • We use string literals. It feels old, bad and error prone.
  • We cast userInfo. The receiver needs to know the type of the payload. If we need to send more parameters we need to have more casting or string constants as keys.
  • The addObserver function is quite long and needs to be repeated for each listener.
  • We have to come up with names for the string literal, notification name variable and the callback method.

A better way?

import UIKit
 
@objc protocol NotificationDelegate: AnyObject {
    @objc optional func didUpdateCar()
    @objc optional func didReceiveData(_ data: [String: Int])
    // ...
}
 

// Place with you other managers
let notificationManager = NotificationManager()

class NotificationManager {
    private var delegates = [Weak<NotificationDelegate>]()
 
    func addDelegate(_ delegate: NotificationDelegate) {
        delegates.append(Weak(value: delegate))
    }
 
    func removeDelegate(_ delegate: NotificationDelegate) {
        if let indexToRemove = delegates.index(where: { $0.value === delegate }) {
            delegates.remove(at: indexToRemove)
        }
    }
 
    func didUpdateCar() {
        delegates.forEach { $0.value?.didUpdateCar?() }
    }
 
    func didReceiveData(_ data: [String: Int]) {
        delegates.forEach { $0.value?.didReceiveData?(data) }
    }
}

The solution is quite straight forward. We create one optional func in our protocol for each type of notification. And we also create one function that broadcasts to all delegates.

NotificationManager is then stored in whatever place you prefer. If you want it as a singleton it can be done, but I prefer to have it as an instance somewhere.

And in Weak.swift we define a container for weak references. This is needed as normal arrays hold a strong reference to it’s elements.

class Weak<T: AnyObject> {
    weak var value: T?
    init (value: T) {
        self.value = value
    }
}

And here is how you use NotificationManager:

class ExampleViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        notificationManager.addDelegate(self)
    }
 
    deinit {
        notificationManager.removeDelegate(self)
    }
}
 
extension ExampleViewController: NotificationDelegate {
    func didReceiveData(_ data: [String: Int]) { 
        for (name, ageInYears) in data {
            print("\(name) is \(ageInYears) years old.")
        }
     }
}

As you can see, the setup becomes so much cleaner, one row for adding self as delegate, and one for removing. Then you extend your class and implement the optional functions of the NotificationDelegate protocol you want to listen to. That means that even if you listen to several notification you still need only that addDelegate one-liner as a setup!

The broadcasting becomes quite clean as well:

class AnotherViewController: UIViewController {
    func didClickButton() {
        let data = ["Jonas": 20, "Andrea": 3, "Oscar": 42]
        notificationManager.didReceiveData(data)
    }
}

Here you can setup parameters in any way you want, no more casting from userInfo.

To summarize:

  • No more string literals
  • No more casting, we can pass along multiple typed parameters
  • The addDelegate function is super short and is only needed once per class
  • We still need to come up with two names, for the callback function and the corresponding function in NotificationManager, but I usually use the same

Hope you liked the solution. Let me know what you think in the comments. Can this perhaps be improved even further?

要查看或添加评论,请登录

Jonas Andersson的更多文章

  • 7 f?rdelar med att anv?nda low-code f?r apputveckling

    7 f?rdelar med att anv?nda low-code f?r apputveckling

    Low code ?r en revolutionerande metod f?r apputveckling som har vuxit i popularitet de senaste ?ren. Ist?llet f?r att…

    1 条评论
  • ?r det nu det lyfter?

    ?r det nu det lyfter?

    Nu var kontraktet p?skrivet! Fick mitt hittills st?rsta uppdrag att bygga en app med mitt egenutvecklade ramverk och…

    28 条评论
  • Mitt 2018

    Mitt 2018

    Ett h?ndelserikt ?r ?r slut och innan jag l?gger ut n?got mer p? LinkedIn t?nkte jag h?r kort sammanfatta ?rets som…

    5 条评论
  • New logo from 99designs.com

    New logo from 99designs.com

    Since I recently started my own company working as a freelance app developer I figured I needed a company logo. In this…

    1 条评论
  • Help me pick a logo (second round)

    Help me pick a logo (second round)

    Help me find a good logo for my (app freelance consultant) company: Focus on the logo, the text can easily be changed…

    4 条评论
  • Reskollen f?r V?sttrafik finns nu f?r Android

    Reskollen f?r V?sttrafik finns nu f?r Android

    Reskollen f?r V?sttrafik finns nu f?r Android! ?ver ett ?r efter att iOS-versionen sl?ppts finns den nu p? ?ntligen…

    2 条评论

社区洞察

其他会员也浏览了