Communicate between objects using channels

When you have two objects A and B, say two view controllers, that you want to make talk to each other, you can choose from the following options:

  • NSNotificationCenter. This is anonymous one-to-many communication. Object A posts a notification to the NSNotificationCenter, which then distributes it to any other objects listening for that notification, including Object B. A and B do not have to know anything about each other, so this is a very loose coupling. Maybe a little too loose…
  • KVO (Key-Value Observing). One object observes the properties of another. This is a very tight coupling, because Object B is now peeking directly into Object A. The advantage of KVO is that Object A doesn’t have to be aware of this at all, and therefore does not need to send out any notifications — the KVO mechanism takes care of this behind the scenes.
  • Direct pointers. Object A has a pointer to Object B and directly sends it messages when something of interest happens. This is the tightest coupling possible because A and B cannot function without each other. In the case of view controllers you generally want to avoid this.
  • Delegates. Object B is a delegate of Object A. In this scenario, Object A does not know anything about Object B. It just knows that some object performs the role of its delegate and it will happily send messages to that delegate, but it doesn’t know — or care — that this is Object B. The delegate pattern is often the preferred way to communicate between view controllers, but it takes some work to set up.
  • Blocks. Essentially the same approach as delegates, except that Object B now gives Object A one or more blocks (closures) to be executed when certain events take place. There is no formal delegate protocol and the only thing that Object A sees of Object B is the blocks it is given.

That is quite a few possibilities, each with its advantages and disadvantages, and the trick is to pick the one that is best suited for the particular communication problem you’re trying to solve.

In the end, they are all variations on the observer-notifier pattern. And if none fit, you can always write your own version. :-)

Choose your channels

I was playing with another mechanism that I call “channels” that is a mix of NSNotificationCenter, delegates and blocks. Rather than making Objects A and B communicate directly, they do this through an intermediary, the channel:

Object A and Object B communicate through a channel

This is perfectly achievable using NSNotificationCenter (either the global one or a private one for each channel), but I wanted something a bit more lightweight that is simpler to use.

A channel is identified simply by a name, an NSString. If two or more objects use the same channel name, then they are communicating. An object can post a message to the channel and/or listen for messages from other objects.

To listen to a channel you simply do:

[self mh_listenOnChannel:@"MyChannel" block:^(id sender, NSDictionary *dict)
{
    // do something with the dictionary
}];

To post a message to that same channel:

NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:...];
[self mh_post:dictionary toChannel:@"MyChannel"];

Or with the new Objective-C literal syntax:

[self mh_post:@{ @"Success" : @YES } toChannel:@"MyChannel"];

The dictionary contains any data that you wish to send along. For example, a view controller that is closing could set a field that indicates which button the user tapped, Cancel or Save.

And that’s all you have to do to make these two objects communicate. When an object calls mh_post:toChannel:, any blocks that were registered by listeners for that channel will be executed.

There is no need to unregister from a channel (although you can if you want to). The channel only keeps weak references to its listeners, so if a listener gets deallocated the channel will no longer try to send it notifications.

This functionality is implemented as a category on NSObject. You can find the source code at Github.

Comments

  1. Paul Denlinger says:

    Thank you for the article.

    I am just curious; what is the use case for this? What are the meaningful advantages it offers over the other communications formats?

  2. Stan Bright says:

    Awesome technique. I’ll try to for sure next time I need something similar.

    Thanks!

  3. Matthijs says:

    @Paul Denlinger: I’m not sure yet. :-) With delegation there is always one listener, with notifications there may be many. This sits somewhere in between — instead of one-to-one or one-to-many it’s more like one-to-several. It’s a simpler version of NSNotificationCenter, somewhat like Java’s listeners but without the notifier having to keep a list of registered listener objects around.

  4. David Birdsall says:

    @Paul Denlinger it’s the same as an “event queue” or “message bus” – it allows you to decouple your producer from consumers – so your producer doesn’t need to manage the references to the consumers – consumers can just pop in and out of existence, register/unregister when they are hidden/become visible etc.

    e.g. https://github.com/Wolfy87/EventEmitter/ in Javascript.

    The only thing with these channels/queues and posted messages is that sometimes you want to know which object created the message (depending on how you’re using them.)

    Good blog post though !

  5. Morel says:

    Normally you can do almost anything using kvo if your ui document objects are well designed. They are the bridge between the ui ccontrollers, flow manager and other components. I totally prefer registering for changes and react than being notified because this creates a dependency between various elements that should not be dependent.

  6. Cassius says:

    Congratulations for the post and the component has been very useful to me!

    I noticed something strange (but could not identify any pattern), the application closes by trying to enumerate an array being modified in the “for” of method:

    - (void)mh_post:(NSDictionary *)dictionary toChannel:(NSString *)channelName

    I could not fix this problem, you know how to solve this?

    Thank you!

  7. Matthijs says:

    @Cassius You cannot modify any collection that you’re enumerating at that time. This is not allowed in Cocoa.

  8. Cassius says:

    I know that, I’m not modifying any collection. Using an “exception breakpoint” the problem points to the “for” of method mh_post of your component. But thanks anyway!

  9. Matthijs says:

    @Cassius I’m going to need a bit more info if you want me to figure out what is going wrong for you. ;-)

  10. Cassius says:

    You can send me an email so I send you some more informations and print log of the problem? Thank you!

  11. Sjors Hollemans says:

    Hi Matthijs,

    Randomly found your blog and find it most interesting to read about iOS development.

    I recently started to delve into learning to create an application because my brother in law and I came up with a great idea for an app with web-app.

    When I am more acquainted with Xcode and Objective-C I will definitely contact you if that’s ok :)

    btw, could it be that we are related ? :P

Leave a Reply

Your email address is required but will not be visible to others.

 *
 *