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.
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)
}
}
Leave a Reply