Better than events

I’ve received some feedback regarding using event-based architecture in my recent post on my Tactics RPG State Machine. The concern is that because events cause extra memory allocations it could have an affect on performance. I’ve used events heavily in every project I’ve worked on and to date have never observed a performance problem on their account. Still, I was curious to run some tests and see just how bad it might be.

Native Event Test

I had recently read a post in a forum regarding poor perfance due to the unregistering of events causing a noticeable hitch in loading time, due to garbage collection. The user had a case where several hundred objects had registered for an event. My first thought would be that several hundred objects shouldn’t be a big deal, and that an average game could easily create comparable scenarios. However, when I thought about it, the numbers started to add up enough that it felt worthy of inspection.

One problem is that events are immutable. EVERY time you add or remove a listener, a new allocation must be made. Suppose you had 250 objects registering for an event. Not only do you get allocations to create the delegate (the event listener) but also allocations for the event itself with each and every add, and then each and every remove of the delegate.

The test I decided to make creates this sort of scenario. I made a script which creates 250 objects, each of which add themselves as an event listener, then the script fires its event, and then all of the objects remove themselves from being an event listener. I made this test trigger based off a key press, so I could fire it repeatedly and see how it looks in the profiler.

I created a new project, then created the following script and attached it to a Camera in the scene. Note that it is wrapped in a namespace which allows me to make a few variations of this test using classes with the same names (organize it in Unity by making appropriately named folders for each test).

using UnityEngine;
using System;
using System.Collections;

namespace Events.Test1
{
	public class Demo : MonoBehaviour 
	{
		public static event EventHandler testEvent;
		public int listenerCount = 250;

		void Update ()
		{
			if (Input.GetKeyUp(KeyCode.Space))
				Test();
		}

		void Test ()
		{
			Listener[] listeners = new Listener[listenerCount];
			for (int i = 0; i < listenerCount; ++i)
			{
				listeners[i] = new Listener();
				listeners[i].Enable();
			}

			if (testEvent != null)
				testEvent(this, EventArgs.Empty);

			for (int i = 0; i < listeners.Length; ++i)
				listeners[i].Disable();
		}
	}

	public class Listener 
	{
		public void Enable ()
		{
			Demo.testEvent += OnTest;
		}
		
		public void Disable ()
		{
			Demo.testEvent -= OnTest;
		}
		
		void OnTest(object sender, EventArgs info)
		{
			
		}
	}
}

Make sure the profiler is open, from the file menu choose Window->Profiler, and then run the scene. Wait a moment to make sure the profiler is tracking and has some data to compare against, and then press the space bar one or more times with a bit of pause between presses. You should see some spikes, both in the CPU and in Memory. I have attached a sample showing the memory spikes for allocations:

Memory Spikes_zpsnczlufat

Pause the running scene, and then drag your mouse over the area where the memory spike occurs. At the bottom of the profiler window you will see information for that frame in time:

Memory Spikes_zps2y0yj1qy

Both before and after the test is actually triggered, the GC Allocations per Frame was 0 MB. When the test is triggered it jumps to 6.3 MB. When you have limited amounts of memory (such as on a mobile platform), that might be concerning.

Unity Event Test

Out of curiosity I decided to make a similar test using Unity Events instead of Native Events. Perhaps they have done something more efficient which doesn’t require the event to be an immutable object. For reference sake, this test was generated while using Unity Version 5.0.1. I made a new scene, and attached the following script to the camera then ran the test with the profiler again:

using UnityEngine;
using UnityEngine.Events;
using System;
using System.Collections;

namespace UnityEvents.Test1
{
	public class Demo : MonoBehaviour 
	{
		public static UnityEvent testEvent = new UnityEvent();
		public int listenerCount = 250;
		
		void Update ()
		{
			if (Input.GetKeyUp(KeyCode.Space))
				Test();
		}
		
		void Test ()
		{
			Listener[] listeners = new Listener[listenerCount];
			for (int i = 0; i < listenerCount; ++i)
			{
				listeners[i] = new Listener();
				listeners[i].Enable();
			}

			testEvent.Invoke();
			
			for (int i = 0; i < listeners.Length; ++i)
				listeners[i].Disable();
		}
	}

	public class Listener 
	{
		public void Enable ()
		{
			Demo.testEvent.AddListener(OnTest);
		}
		
		public void Disable ()
		{
			Demo.testEvent.RemoveListener(OnTest);
		}
		
		void OnTest()
		{

		}
	}
}

This time the results were far better, a mere 111.4 KB

Notification Center Test

Although the Unity Event test was very promising, I was also curious about an alternative for events I had created in an earlier post, Social Scripting Part 3 – my Notification Center.

When I began thinking about why events were immutable I realized that my implementation of the Notification center was flawed. In a use-case where the Notification Center would either add or remove an event handler while invoking a notification, a handler could be skipped – or worse, the iterator could go out of bounds. Crash!

To get around this problem I modified the script so that anytime a user tries to add or remove a handler from a list of handlers which is being invoked, that a duplicate event handler list would be created and added at that time. Otherwise, I could just keep reusing the same list with no need of extra allocations.

Additionally, I decided to leave lists in place even after the unregistering of an event causes the list count to drop to zero. This way, if another event is added later, I already have a list allocated and with an appropraite capacity. I added a separate method to clean up the tables and remove entries with no handlers which could be called at key times (like after switching scenes etc).

I also decided to move away from the native EventHandler delegate, and instead used an Action which takes two objects – this way I wasn’t required to have my arguments be a subclass of EventArgs – it can now be anything, even a value type object.

The revised script follows:

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

/// <summary>
/// This delegate is similar to an EventHandler:
/// 	The first parameter is the sender, 
/// 	The second parameter is the arguments / info to pass
/// </summary>
using Handler = System.Action<System.Object, System.Object>;

/// <summary>
/// The SenderTable maps from an object (sender of a notification), 
/// to a List of Handler methods
///		* Note - When no sender is specified for the SenderTable, 
/// 		the NotificationCenter itself is used as the sender key
/// </summary>
using SenderTable = System.Collections.Generic.Dictionary<System.Object, System.Collections.Generic.List<System.Action<System.Object, System.Object>>>;

public class NotificationCenter
{
	#region Properties
	/// <summary>
	/// The dictionary "key" (string) represents a notificationName property to be observed
	/// The dictionary "value" (SenderTable) maps between sender and observer sub tables
	/// </summary>
	private Dictionary<string, SenderTable> _table = new Dictionary<string, SenderTable>();
	private HashSet<List<Handler>> _invoking = new HashSet<List<Handler>>();
	#endregion
	
	#region Singleton Pattern
	public readonly static NotificationCenter instance = new NotificationCenter();
	private NotificationCenter() {}
	#endregion
	
	#region Public
	public void AddObserver (Handler handler, string notificationName)
	{
		AddObserver(handler, notificationName, null);
	}
	
	public void AddObserver (Handler handler, string notificationName, System.Object sender)
	{
		if (handler == null)
		{
			Debug.LogError("Can't add a null event handler for notification, " + notificationName);
			return;
		}
		
		if (string.IsNullOrEmpty(notificationName))
		{
			Debug.LogError("Can't observe an unnamed notification");
			return;
		}

		if (!_table.ContainsKey(notificationName))
			_table.Add(notificationName, new SenderTable());

		SenderTable subTable = _table[notificationName];

		System.Object key = (sender != null) ? sender : this;

		if (!subTable.ContainsKey(key))
			subTable.Add(key, new List<Handler>());

		List<Handler> list = subTable[key];
		if (!list.Contains(handler))
		{
			if (_invoking.Contains(list))
				subTable[key] = list = new List<Handler>(list);

			list.Add( handler );
		}
	}
	
	public void RemoveObserver (Handler handler, string notificationName)
	{
		RemoveObserver(handler, notificationName, null);
	}
	
	public void RemoveObserver (Handler handler, string notificationName, System.Object sender)
	{
		if (handler == null)
		{
			Debug.LogError("Can't remove a null event handler for notification, " + notificationName);
			return;
		}

		if (string.IsNullOrEmpty(notificationName))
		{
			Debug.LogError("A notification name is required to stop observation");
			return;
		}
		
		// No need to take action if we dont monitor this notification
		if (!_table.ContainsKey(notificationName))
			return;
		
		SenderTable subTable = _table[notificationName];
		System.Object key = (sender != null) ? sender : this;

		if (!subTable.ContainsKey(key))
			return;

		List<Handler> list = subTable[key];
		int index = list.IndexOf(handler);
		if (index != -1)
		{
			if (_invoking.Contains(list))
				subTable[key] = list = new List<Handler>(list);
			list.RemoveAt(index);
		}
	}

	public void Clean ()
	{
		string[] notKeys = new string[_table.Keys.Count];
		_table.Keys.CopyTo(notKeys, 0);

		for (int i = notKeys.Length - 1; i >= 0; --i)
		{
			string notificationName = notKeys[i];
			SenderTable senderTable = _table[notificationName];

			object[] senKeys = new object[ senderTable.Keys.Count ];
			senderTable.Keys.CopyTo(senKeys, 0);

			for (int j = senKeys.Length - 1; j >= 0; --j)
			{
				object sender = senKeys[j];
				List<Handler> handlers = senderTable[sender];
				if (handlers.Count == 0)
					senderTable.Remove(sender);
			}

			if (senderTable.Count == 0)
				_table.Remove(notificationName);
		}
	}
	
	public void PostNotification (string notificationName)
	{
		PostNotification(notificationName, null);
	}
	
	public void PostNotification (string notificationName, System.Object sender)
	{
		PostNotification(notificationName, sender, null);
	}
	
	public void PostNotification (string notificationName, System.Object sender, System.Object e)
	{
		if (string.IsNullOrEmpty(notificationName))
		{
			Debug.LogError("A notification name is required");
			return;
		}
		
		// No need to take action if we dont monitor this notification
		if (!_table.ContainsKey(notificationName))
			return;
		
		// Post to subscribers who specified a sender to observe
		SenderTable subTable = _table[notificationName];
		if (sender != null && subTable.ContainsKey(sender))
		{
			List<Handler> handlers = subTable[sender];
			_invoking.Add(handlers);
			for (int i = 0; i < handlers.Count; ++i)
				handlers[i]( sender, e );
			_invoking.Remove(handlers);
		}
		
		// Post to subscribers who did not specify a sender to observe
		if (subTable.ContainsKey(this))
		{
			List<Handler> handlers = subTable[this];
			_invoking.Add(handlers);
			for (int i = 0; i < handlers.Count; ++i)
				handlers[i]( sender, e );
			_invoking.Remove(handlers);
		}
	}
	#endregion
}

And the companion script I use to help decouple the Notification Center from other classes:

using UnityEngine;
using System;
using System.Collections;
using Handler = System.Action<System.Object, System.Object>;

public static class NotificationExtensions
{
	public static void PostNotification (this object obj, string notificationName)
	{
		NotificationCenter.instance.PostNotification(notificationName, obj);
	}
	
	public static void PostNotification (this object obj, string notificationName, object e)
	{
		NotificationCenter.instance.PostNotification(notificationName, obj, e);
	}
	
	public static void AddObserver (this object obj, Handler handler, string notificationName)
	{
		NotificationCenter.instance.AddObserver(handler, notificationName);
	}
	
	public static void AddObserver (this object obj, Handler handler, string notificationName, object sender)
	{
		NotificationCenter.instance.AddObserver(handler, notificationName, sender);
	}
	
	public static void RemoveObserver (this object obj, Handler handler, string notificationName)
	{
		NotificationCenter.instance.RemoveObserver(handler, notificationName);
	}
	
	public static void RemoveObserver (this object obj, Handler handler, string notificationName, System.Object sender)
	{
		NotificationCenter.instance.RemoveObserver(handler, notificationName, sender);
	}
}

Next is the test code itself:

using UnityEngine;
using System.Collections;

namespace Notifications.Test1
{
	public class Demo : MonoBehaviour 
	{
		public const string Notification = "Demo.Notification";
		public int listenerCount = 250;

		void Update ()
		{
			if (Input.GetKeyUp(KeyCode.Space))
				Test();
		}

		void Test ()
		{
			Listener[] listeners = new Listener[listenerCount];
			for (int i = 0; i < listenerCount; ++i)
			{
				listeners[i] = new Listener();
				listeners[i].Enable();
			}
			
			this.PostNotification(Notification);
			
			for (int i = 0; i < listeners.Length; ++i)
				listeners[i].Disable();

			NotificationCenter.instance.Clean();
		}
	}

	public class Listener 
	{
		public void Enable ()
		{
			this.AddObserver(OnTest, Demo.Notification);
		}
		
		public void Disable ()
		{
			this.RemoveObserver(OnTest, Demo.Notification);
		}
		
		void OnTest(object sender, object info)
		{

		}
	}
}

The results of this test were the best yet, coming in around 54 to 63 KB!

Test 2

After realizing the initial flaw I had with my Notification Center, I wanted to test and see what the results would look like when I actually had to reallocate a list due to modifications within an invoke. That’s kind of a mouth-full and probably hard to understand, so imagine a scenario like this…

  1. Multiple objects register for an event
  2. The event is triggered, and the methods begin being iterated over
  3. Within the event handler delegate, one (or more) of the listeners unsubscribes itself
  4. In order for the original list of handlers to be called without missing any (and without calling new handlers that may have been added after the event is triggered) a copy of the list would be required – this was the fix needed in my Notification Center, but the Native Event and Unity Event already handled this case just fine.

For each of the tests, I only needed to modify the Listener class. The Native Event based sample follows:

public class Listener 
{
	public static event EventHandler clearEvent;
	
	public void Enable ()
	{
		Demo.testEvent += OnTest;
		Listener.clearEvent += OnClear;
	}
	
	public void Disable ()
	{
		Demo.testEvent -= OnTest;
		Listener.clearEvent -= OnClear;
	}
	
	void OnTest(object sender, EventArgs info)
	{
		if (clearEvent != null)
			clearEvent(this, EventArgs.Empty);
	}
	
	void OnClear (object sender, EventArgs info)
	{
		Demo.testEvent -= OnTest;
	}
}

Here is the modification for the Unity Event based sample:

public class Listener 
{
	public static UnityEvent clearEvent = new UnityEvent();
	
	public void Enable ()
	{
		Demo.testEvent.AddListener(OnTest);
		Listener.clearEvent.AddListener(OnClear);
	}
	
	public void Disable ()
	{
		Demo.testEvent.RemoveListener(OnTest);
		Listener.clearEvent.RemoveListener(OnClear);
	}
	
	void OnTest()
	{
		clearEvent.Invoke();
	}
	
	void OnClear ()
	{
		Demo.testEvent.RemoveListener(OnTest);
	}
}

And finally, the modification for my Notification Center based sample:

public class Listener 
{
	public const string Clear = "Listener.Clear";
	
	public void Enable ()
	{
		this.AddObserver(OnTest, Demo.Notification);
		this.AddObserver(OnClear, Listener.Clear);
	}
	
	public void Disable ()
	{
		this.RemoveObserver(OnTest, Demo.Notification);
		this.RemoveObserver(OnClear, Listener.Clear);
	}
	
	void OnTest(object sender, object info)
	{
		this.PostNotification(Listener.Clear);
	}
	
	void OnClear (object sender, object info)
	{
		this.RemoveObserver(OnTest, Demo.Notification);
	}
}

Ready for the results? As I expected, the native event test scored the worst. It spiked at a whopping 18.7 MB! The Unity Event based test was the next best, but scored unexpectedly poor – it spiked at 14.5 MB! My Notification Center example won by a landslide with a spike of only 6.3 MB!

Summary

After all of these tests, it appears that using native events can actually cause noticeably large spikes in memory and garbage collection. I can definitely see a case here against architectures which rely too heavily on events that are continually added and removed. Using Unity’s event system offered noticeable savings, but still scored relatively poorly in the second test variation. Revisiting my own Notification Center offered the best scores by far, and reconfirmed to me that it is a tool worth investing more time in.

Regardless of which approach I took, it was also impressed upon me how bad an idea it is to modify an event handler list while the event is being invoked. Even though each of the three methods can handle it, the impact on memory is huge. In the future I will be extra sure to only add and remove listeners in places that are not connected to the event handlers themselves.

I would love to hear feedback from my fellow developers on this. Opinions? Suggestions? If you see a way for my script to improve please feel free to share!

22 thoughts on “Better than events

  1. I’ve always liked the NSNotificationCenter style, and I’m glad to see someone has built a similar system for C#/Unity.

    What license is your code released under?

  2. As I’m taking a break for the evening, I figured I should leave a comment since I’ve been using these tutorials of yours.

    They are quite fantastic and so far has been the farthest I’ve stuck with building out a game. In the past I’ve tried building an engine from scratch since lower level coding is my bread and butter (I had a project written in C++ currently unfinished and another written in Go that is also currently unfinished haha. I have a bad habit of doing that).

    Anyways, when I decided to try out using Unity i wasn’t expecting to find a tutorial so clearly geared towards where I was trying to go (though not exactly what I want to do, I’m planning on a purely 2D game). However, the vast majority of things I’m learning here are going to end up being reusable for me and are an invaluable introduction to C# and to Unity in general.

    I’m looking forward to continuing on these tutorials and whatever other fun stuff you have in store to go around. I’ve built infrastructures similar to the Notification Center before in C++ so seeing something familiar implemented in C# was nice 🙂

    I was also able to easily plug it into the previous code and replace the usage of events with the notification centers for the InputController and such which was awesome. Anyways, thanks for these!

  3. Would you recommend that we go through the old code and replace the old event system with the new notification system?

    1. I think the notification system is superior, but not so much so that it makes a notable difference in most use cases. It would be a good exercise though, and I would probably do it myself. So in short, yep. 🙂

  4. This is so neat. I especially like how de-coupled this system is. I do still have some questions about some additional features I could use:

    1: I’m comparing this to the event system they make over here: https://unity3d.com/learn/tutorials/modules/intermediate/live-training-archive/events-creating-simple-messaging-system. While that one has fewer features (no arguements for one), the lecturer makes the arguement around 53:00 that using UnityEvents for the implementation have the advantage of more easily hooking into the in-built Unity systems that already use UnityEvents, such as the GUI. Would making a version using UnityEvents with all the same features be possible? Would it be a bad idea?

    2: It’s ultimately not a big deal, but I feel it’s a bit inelegant that you always have to include the sender and info objects as parameters for your “Handler” methods, even if they don’t use them. Would it be feasible to use overloading to allow for Handlers with different amounts of parameters? As is, the system seems very wound up in the handler being an Action, but I would figure it could support other kinds of Actions with some elbow-grease.

    3: Along the same vein, having to always cast the “info” from object feels a bit inelegant. While overloading for every type of parameter would be infeasible, having a few of the most common simple types (int, bool, float, string) would make a lot of code look nicer. Could that be done? If errors in typing between triggers and observers could be detected at compile-time, rather than run-time, that’d be even better, though I doubt that’s possible.

    I don’t expect you to post any implementation details, but if you could point me in the right direction for implementing these things myself, or whether it’s a waste of time to try, that’d be great 🙂

    1. Thanks, glad you liked it. I considered doing a notification center based on Unity events but overall I found no added value. I haven’t listened to that lesson although I just now skimmed over the code. From what I can see there is nothing there that ties in to Unity systems any easier that what I have done – it looks like you would still need code to do pretty much everything. Is there better elaboration on this topic?

      Overall the pattern I ended up following was based on the idea of .Net’s EventHandler. I simply decided I didn’t like having to use a subclass of EventArgs for the argument parameter, otherwise I would have used that delegate directly. It seems to me that it is a very flexible system and still pretty efficient. Plus, anyone who has used EventHandler would be able to adapt easily.

      I also would love a way to let the type of the sender and arguments be known ahead of time. I experimented with a few different implementations (like generics) but everything proved too difficult to get right, if not impossible, for the approaches I was taking.

      I suppose pointing to the .net EventHandler is also “evidence” that there aren’t a lot of good options, or at least no intuitive ones, in C# for getting what you want. It doesn’t mean that what you want is impossible, or even that it would be a waste of time to try, but perhaps, not worth trying if you are not an experienced programmer. If you feel like you find something better, I’d love to hear your ideas.

  5. Hi there! I understand this is an old post at this point, hoping you still maintain this.

    I’m absolutely in love with your Tactics RPG series, it’s my favorite style of game and man… am I learning a LOT about game architecture from following along with you. I’ve run into a bit of a noob problem with this one, though.

    Honestly, I’m not 100% sure how the native implementation of EventHandler works, and while I understand that you are sort of… remaking that class, I definitely cannot figure out how to change the code from past lessons to utilize NotificationCenter. I see that in your current repo, you are still using InfoEventArgs for your events… so I couldn’t cheat, either.

    Any chance you could assist a noobie in understanding the difference in implementation?

    1. Glad you are enjoying the Tactics RPG series! I didn’t update the EventHandler stuff because ultimately you don’t “need” to. Even though the notification center stuff is faster in my tests, they are both fast enough that the difference in efficiency is unlikely to have any noticeable effect.

      The EventHandler isn’t actually a class. It is a special kind of delegate. You can think of it as a variable which points to a method(s) instead of data. You assign it a method and then invoke it/them when something special happens and in this way are able to “inject” functionality into a program.

      In typical use with the EventHandler, a developer will need to know about the class which defined the method to be invoked and the class which will need to trigger it, which often causes the code to become tightly coupled – this just means that when too much stuff knows about each other they become dependent on each other and it becomes harder to reuse your code and/or change it later. In addition, only the instance or class which holds the EventHandler can invoke it.

      The idea behind the Notification Center was to help “decouple” classes so that they don’t care who defines a method or who triggers it. They just know they can listen to and or post notifications and maybe something will happen. Unlike with EventHandlers, any object can post any notification.

      I did a series of posts that cover Event Handling and the original creation of the Notification Center (though note I have made some changes). Still, it should help explain how all of this stuff works:

      Social Scripting
      Part 1
      Part 2
      Part 3

      1. Ah! I had gone through part 2, as suggested during your intro to this series, but going through the rest of that miniseries definitely helped me conceptualize Event Handling a lot better (it’s so easy in programming tutorials to write someone else’s code and pretend like I know what I’m writing…)

        Thanks so much! Your grasp of these concepts is very impressive and I am learning a lot from you. I hope to take what I’m learned from this series and flesh it into something of my own! But man… this game development stuff can be daunting

        1. I’m glad that helped to clear things up. Yeah, games are big projects, but just break it down into bite sized chunks and you can work toward making anything. If you get stuck on your own project you can always post in my forum and I’ll be happy to give advice as I have time.

  6. First, I just want to say thanks for a really nice Notification Center implementation.

    I have a question though, maybe its a typo:

    In this section:
    // Post to subscribers who did not specify a sender to observe
    if (subTable.ContainsKey(this))
    {
    List handlers = subTable[this];
    _invoking.Add(handlers);
    for (int i = 0; i < handlers.Count; ++i)
    handlers[i]( sender, e );
    _invoking.Remove(handlers);
    }

    wouldn't you want to call handlers[i](this, e); instead?

    In the scenario where you pass a sender in PostNotification, subscribers who did not specify a sender would incorrectly receive the sender that was passed instead.

    Unless I am missing something?

    Thanks again!

    1. You’re welcome, glad you like it. To answer your question, there is no typo here. We want to use the actual sender of a notification as long as whoever posted the notification actually provided something. Listeners can choose to receive notifications only for a single sender by registering only for a particular sender if they want to, but there are times they want to receive the notification no matter who sends it, and still want to know who sent it.

      For example, say you have some sort of Manager class that is responsible for cleaning up enemies that have died. It can register once without specifying a sender and then it will receive all death notifications. Since the original sender is used, the Manager class can correctly delete or recycle etc the object that died.

  7. Also, this line:
    if (sender != null && senders.ContainsKey(sender))

    If you specify the sender in PostNotification as the NotificationCenter itself, the notification will be invoked twice, but I guess you can skip the first invoke by doing this:

    if (sender != null && sender != this && senders.ContainsKey(sender))

    Thoughts?

    1. All notifications can be sent more than once (note that we are using a for loop and posting potentially many times), this is true even for events etc. It will send one notification to each subscriber. If a single subscriber registers to listen to a notification sent by “anyone” as well as a notification sent by “specified object” then we would expect it to receive two calls back. Usually in practice for this type of case the handler method you registered with would be different and you want them both to be called.

      If you ONLY register either for the “anyone” or “specific object” notification then you will only receive one notification. Try it out for yourself to verify.

      1. I understand. However, consider this small example:

        void Start() {
        NotificationCenter.instance.AddObserver(“test_notification”, testHandler);

        NotificationCenter.instance.PostNotification(“test_notification”, NotificationCentre.instance, “doing this will cause the handler to be fired twice.”);

        NotificationCenter.instance.PostNotification(“test_notification”, null, “doing this will cause the handler to be fired once.”);

        NotificationCenter.instance.RemoveObserver(“test_notification”, testHandler);

        NotificationCenter.instance.Clean();
        }

        In that example, only one notification is registered with only one observer, and as the sender isn’t specified, its sender is automatically set to NotificationCenter.instance.
        Because of this, however, the first posted notification will cause the handler to be fired twice. The second posted notification will cause the handler to be fired once.

        I know I’m being pedantic; in practice, nobody would (should?) ever do this. I’m just not sure that this is expected behaviour.

        Thanks again 🙂

        1. Ok, now I understand what you are saying. You never know how someone will use your code 🙂 You are right, I wouldn’t expect the notification center to post a notification twice if the notification center itself was the sender. However, anyone doing this is sort of “lying” because I do not tell the notification center itself to post anything. Any notification generated and posted in this way should hopefully feel wrong enough that no-one will do that. Still a good catch though, I can see you are really studying it all out.

Leave a Reply

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