How to implement a custom Notification Center in Swift

Notifications are a very helpful tool, when you need to send messages between two modules which are very loosely coupled. In this blog post, I will implement my own NSNotificationCenter in Swift.

Notifications are used to announce messages to other components which are listening.

Let’s start with the basic data structure needed to hold a Notification. I am using a Dictionary, which contains an array to hold the notifications. You can alter this data structure according to your requirements.

class Notification {
	var name : String
	var objectName : AnyObject
	var methodName : Selector
	var userInfo : Dictionary<String, Any>?
	var object : AnyObject?
	init(thisName: String, thisClassName: AnyObject, thisMethodName: Selector, thisObject : AnyObject?) {
		self.name = thisName
		self.objectName = thisClassName
		self.methodName = thisMethodName
		self.object = thisObject
	}
}

Each app will have a single notification center. I am going with a singleton to accomplish this. I will be using a Dictionary to store the notifications. The key will be the notification name. The value will be an array of notifications.

class NotificationCenter : NSObject {
	var notifications : Dictionary<String, [Notification]>?
	static let sharedInstance : NotificationCenter = {
		let instance = NotificationCenter()
		instance.initVars()
		return instance
	}()
	
	func initVars() {
		notifications = Dictionary<String, [Notification]>()
	}

Read more about this API here addObserver API

Adds an entry to the notification center’s dispatch table with an observer and a notification selector, and an optional notification name and sender. The logic is very simple. We first check if a notification is present in the dictionary, if yes, we append the notification, else we create a new one and add it.

	func addObserver(notificationName:String, objectName:AnyObject, methodName:Selector, object:AnyObject?) {
		let notification = Notification(thisName: notificationName, thisClassName: objectName, thisMethodName: methodName, thisObject: object)
		var array = notifications![notificationName]
		guard array != nil else {
			array = Array<Notification>()
			array?.append(notification)
			notifications![notificationName] = array
			return
		}
		notifications![notificationName]?.append(notification)
	}

More about this API postNotification

I use the perform API to call the method after retrieving the notification from the dictionary.

	func postNotification(notificationName: String, object: AnyObject) {
		let notification = notifications![notificationName]
		guard notification != nil else {
			return
		}
		for notify in notification! {
			let objectName = notify.objectName
			let methodName = notify.methodName
			if object === notify.object {
				_ = objectName.perform(methodName, with: object)
			}
		}
	}

I have implemented the removeObserver too. The entire code is shown below.

import UIKit

class Notification {
	var name : String
	var objectName : AnyObject
	var methodName : Selector
	var userInfo : Dictionary<String, Any>?
	var object : AnyObject?
	init(thisName: String, thisClassName: AnyObject, thisMethodName: Selector, thisObject : AnyObject?) {
		self.name = thisName
		self.objectName = thisClassName
		self.methodName = thisMethodName
		self.object = thisObject
	}
}

class NotificationCenter : NSObject {
	var notifications : Dictionary<String, [Notification]>?
	static let sharedInstance : NotificationCenter = {
		let instance = NotificationCenter()
		instance.initVars()
		return instance
	}()
	
	func initVars() {
		notifications = Dictionary<String, [Notification]>()
	}
	
	func addObserver(notificationName:String, objectName:AnyObject, methodName:Selector, object:AnyObject?) {
		let notification = Notification(thisName: notificationName, thisClassName: objectName, thisMethodName: methodName, thisObject: object)
		var array = notifications![notificationName]
		guard array != nil else {
			array = Array<Notification>()
			array?.append(notification)
			notifications![notificationName] = array
			return
		}
		notifications![notificationName]?.append(notification)
	}
	
	func postNotification(notificationName: String) {
		
		guard let notification = notifications![notificationName] else {
			return
		}
		for notify in notification {
			let objectName = notify.objectName
			let methodName = notify.methodName
			_ = objectName.perform(methodName)
		}
	}
	
	func postNotification(notificationName: String, object: AnyObject) {
		
		guard let notification = notifications![notificationName] else {
			return
		}
		for notify in notification {
			let objectName = notify.objectName
			let methodName = notify.methodName
			if object === notify.object {
				_ = objectName.perform(methodName, with: object)
			}
		}
	}
	
	func postNotification(notificationName: String, userInfo: Any) {
		
		guard let notification = notifications![notificationName] else {
			return
		}
		for notify in notification {
			let objectName = notify.objectName
			let methodName = notify.methodName
			_ = objectName.perform(methodName, with: ["userInfo" : userInfo])
		}
	}
	
	func removeObserver(objectName: AnyObject, notification: String) {
		var array = notifications![notification]
		guard array != nil else {
			return
		}
		if array!.count > 0 {
			array?.removeAll(where: { $0.objectName === objectName && $0.name == notification})
			notifications![notification] = array
		}
	}
	
	func removeObserver(notification: String) {
		if notification.count > 0 {
			notifications?.removeValue(forKey: notification)
		}
	}
}

You can use the below code to test the notification center.



import UIKit

class ViewController: UIViewController {

	override func viewDidLoad() {
		super.viewDidLoad()
		//testThreading()
		
		let testA = TestA()
		let testB = TestB()
		testA.registerNotifications(object: testB)
		//NotificationCenter.sharedInstance.postNotification(notificationName: "viewisloaded", object: testB)
		//testB.postNotifications()
		let testC = TestC()
		testC.postNotifications()
		testA.removeNotification()
		testB.postNotifications()
	}

let test = NotificationCenter.sharedInstance
let test2 = NotificationCenter.sharedInstance

class TestA {
	func registerNotifications(object : TestB) {
		NotificationCenter.sharedInstance.addObserver(notificationName: "viewisloaded",
													  objectName: self,
													  methodName: #selector(displayNoParams),
													  object: object)
		
		NotificationCenter.sharedInstance.addObserver(notificationName: "viewisloaded1",
													  objectName: self,
													  methodName: #selector(displayClassName(_:)),
													  object: object)
	}
	@objc func displayClassName(_ userInfo : NSDictionary) {
		print("TestA viewisloaded1 ", userInfo.allValues)
	}
	@objc func displayNoParams() {
		print("TestA viewisloaded")
	}
	
	func removeNotification() {
		NotificationCenter.sharedInstance.removeObserver(objectName: self, notification: "viewisloaded")
		NotificationCenter.sharedInstance.removeObserver(notification: "viewisloaded1")
	}
}

class TestB {
	func postNotifications() {
		NotificationCenter.sharedInstance.postNotification(notificationName: "viewisloaded")
		NotificationCenter.sharedInstance.postNotification(notificationName: "viewisloaded1", userInfo: "this is userInfo")
		NotificationCenter.sharedInstance.postNotification(notificationName: "viewisloaded1", userInfo: "this is 2userInfo")
	}
}

class TestC {
	func postNotifications() {
		NotificationCenter.sharedInstance.postNotification(notificationName: "viewisloaded", object: self)
	}
}

5 responses

  1. Francis Avatar
    Francis

    Thanks for posting this. I wonder if you can simplify some of the code. For example, your notification center class could be:

    class NotificationCenter : NSObject {
    var notifications = [String, [Notification]]()
    static let shared = NotificationCenter()
    }

    The semantics are the same, but it does feel a bit cleaner

    1. darshan Avatar
      darshan

      cool, thanks for the tip. I could do that.

  2. bdolewski Avatar
    bdolewski

    Hey, instead of:
    let notification = notifications![notificationName]
    guard notification != nil else {
    return
    }
    for notify in notification! {
    let objectName = notify.objectName
    let methodName = notify.methodName
    _ = objectName.perform(methodName)
    }

    you can jump straight to optional binding in guard statement like this:
    guard let notification = notifications![notificationName] else {
    return
    }
    for notify in notification {
    let objectName = notify.objectName
    let methodName = notify.methodName
    _ = objectName.perform(methodName)
    }

    1. darshan Avatar
      darshan

      yeah, that works too.

      1. darshan Avatar
        darshan

        I have updated the code to reflect the changes.

Leave a Reply

Your email address will not be published. Required fields are marked *