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.

notification center in swift. announcing your messages to other components
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.

Data structure to store my notifications. We are using a dictionary here.
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 to Francis Cancel reply

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