Decoupled ViewModel Messaging (Part 2)
NOTE: All the code shown in this series has been published to codeplex. If you would like to download the framework or the sample application that uses the framework you can find it here: http://courier.codeplex.com/
In my previous post I discussed the ViewModel messaging framework at a high level and went over the basics of the implementation. Most of this is review if you are familiar with the other frameworks that I borrowed ideas from.
In this post I want to touch on the two specific features I wanted to add to the other frameworks I had seen (hence the motivation behind this framework and this series of blog posts). Specifically, the two features I felt would be useful is the ability to explicitly unsubscribe from a particular message and the ability to cache messages for re-broadcast at a later date.
The first feature seems obvious enough. There are times when you want to listen for messages and there are times that you want to stop listening for that message. Many of the frameworks mentioned previously use WeakReferences to store the pointer to the message handler method. This works great when object are being destroyed. The Mediator object, which is responsible for delegating messages to their proper handlers, stores only a WeakReference to the handler and checks just prior to dispatching the message if the endpoint IsAlive. If it is then the message is dispatched. If it isn’t alive, then the endpoint is removed from the list of subscribers. This check looks something like this:
for (int i = weakSubscribers.Count - 1; i > -1; --i)
{
WeakSubscriber weakSubscriber = weakSubscribers[i];
if (!weakSubscriber.IsAlive)
weakSubscribers.RemoveAt(i);
else
subscribers.Add(weakSubscriber.CreateAction());
}
What this does is simple. It walks the collection of subscribers backwards and checks the IsAlive property. The IsAlive property on the WeakSubscriber class is simply looking at the underlying WeakReference.IsAlive property. If we find a dead subscriber we remove it from our list so that we don’t try to dispatch the message to an event handler method that has been destroyed.
This pattern works great is your subscriber objects, that is the object that has the event handler for the message in it, is being destroyed. However, I have situations where my object that contains the handler method for a message is still alive but I don’t want to receive any more messages of a given type from the mediator. In this case I need a way to tell the Mediator to remove from it’s list of subscribers.
To help with this I have added a method to the Messenger class call UnRegisterForMessage.
//Method bodies elided for clarity
public void UnRegisterForMessage(String message, Delegate callback)
{
...
}
This method takes in the message you want to unsubscribe to and the handler you want to remove for that message. For a given message and object can have multiple handlers. This method allows you to unsubscribe a particular handler while, potentially keeping the other handlers subscribed.
On caveat to this approach is how I am passing the reference to the handler method. In the internal unregister process I need a way to determine which handler I am removing from which message. The only way I could find to make this work was to pass a reference to the delegate itself. The remove method inside the MessageToSubscriberMap class looks like this:
internal void RemoveSubscriber(String message, Delegate callback)
{
lock (map)
{
if (map.ContainsKey(message))
{
map[message].RemoveAll(subscriber => subscriber.Method == callback.Method);
}
}
}
This works fine but the usage is a bit strange and has one glaring issue that I have yet to find a solution for. Let me demonstrate. I if I want to unsubscribe from a message called “UserChanged.” I would write something like this:
Mediator.UnRegisterForMessage("UserChanged", (Action<IUser>)OnUserChanged)
Notice the second parameter here. I am the reference to the delegate OnUserChanged and casting it as an action. This will work just fine. However, in some internal projects I have used this framework and noticed other developers do something like this:
Mediator.UnRegisterForMessage("UserChnaged", new Action<IUser>(OnUserChnaged))
Notice the difference? Subtle maybe, but it is critical to understand the problem here. In the second example the second parameter, instead of being cast as an Action<T> a new instance of an Action<T> is being created and passed in. This will not point to the same instance of the delegate passed in on the Register call. This means the unsubscribe will not find a match and will not remove the handler. This means you will continue to get messages broadcast. In general, I am not completely satisfied with the unsubscribe method signature and may change it in the future.
This post is getting a bit long winded so I will stop here and in my next post I will describe message caching and how I went about implementing it.
NOTE: All the code shown in this series has been published to codeplex. If you would like to download the framework or the sample application that uses the framework you can find it here: http://courier.codeplex.com/
0 Comments:
Post a Comment
Subscribe to Post Comments [Atom]
<< Home