Skip to content

Instantly share code, notes, and snippets.

@skeeet
Forked from hollance/Explanation.md
Created July 17, 2012 20:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save skeeet/3131816 to your computer and use it in GitHub Desktop.
Save skeeet/3131816 to your computer and use it in GitHub Desktop.
Communicate between objects using channels

Usage example:

In view controller 1's viewDidLoad:

[self mh_listenOnChannel:@"MyChannel" block:^(id sender, NSDictionary *dictionary)
{
    [self dismissViewControllerAnimated:YES completion:nil];
}];

In view controller 2, after a button is pressed to close it:

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

And that's all you have to do to make these two view controllers communicate. The dictionary contains any data that you wish to send along.

#include <dispatch/dispatch.h>
typedef void (^MHChannelsBlock)(id sender, NSDictionary *dictionary);
/*!
* A "channel" is like a private NSNotificationCenter between just two objects
* (although more are possible).
*
* Instead of making your objects, such as two view controllers, communicate
* directly with one another through sharing pointers or making one a delegate
* of the other, you can have them communicate through a channel. Objects can
* post messages to the channel and/or listen to messages from other objects.
*
* There is no need to create channels before you use them. A channel is
* identified by a unique name (an NSString). If two objects use the same
* channel name, then they are communicating.
*
* The order in which messages are delivered is arbitrary, so if you have more
* than one listener on the channel you should not assume anything about which
* one is called first. To force a delivery order, give each listener its own
* priority. The default priority is 0. Higher priorities go first.
*
* The listener block is always executed synchronously on the thread that the
* poster runs on, except when queue is not nil. If you pass in a queue, then
* the block is called _asynchronously_ on that queue.
*
* The channel keeps a weak reference to any listeners, so you do not have to
* explicitly remove the listener from the channel before it gets deallocated.
*/
@interface NSObject (MHChannels)
- (void)mh_post:(NSDictionary *)dictionary toChannel:(NSString *)channelName;
- (void)mh_listenOnChannel:(NSString *)channelName block:(MHChannelsBlock)block;
- (void)mh_listenOnChannel:(NSString *)channelName priority:(NSInteger)priority queue:(dispatch_queue_t)queue block:(MHChannelsBlock)block;
- (void)mh_removeFromChannel:(NSString *)channelName;
- (void)mh_debugChannels;
@end
#import "NSObject+MHChannels.h"
@interface MHChannelListener : NSObject
@property (nonatomic, weak) id object;
@property (nonatomic, copy) MHChannelsBlock block;
@property (nonatomic, assign) NSInteger priority;
@property (nonatomic, assign) dispatch_queue_t queue;
@end
@implementation MHChannelListener
@synthesize object;
@synthesize block;
@synthesize priority;
@synthesize queue;
- (NSString *)description
{
return [NSString stringWithFormat:@"%@ object = %@", [super description], object];
}
@end
@implementation NSObject (MHChannels)
- (NSMutableDictionary *)mh_channelsDictionary
{
static dispatch_once_t pred;
static NSMutableDictionary *dictionary;
dispatch_once(&pred, ^{ dictionary = [NSMutableDictionary dictionaryWithCapacity:4]; });
return dictionary;
}
- (void)mh_pruneDeadListenersFromChannel:(NSString *)channelName
{
NSMutableDictionary *channelsDictionary = [self mh_channelsDictionary];
NSMutableArray *listeners = [channelsDictionary objectForKey:channelName];
NSMutableSet *listenersToRemove = nil;
for (MHChannelListener *listener in listeners)
{
if (listener.object == nil)
{
if (listenersToRemove == nil)
listenersToRemove = [NSMutableSet set];
[listenersToRemove addObject:listener];
}
}
if (listenersToRemove != nil)
{
for (MHChannelListener *listener in listenersToRemove)
[listeners removeObject:listener];
if ([listeners count] == 0)
[channelsDictionary removeObjectForKey:channelName];
}
}
- (void)mh_post:(NSDictionary *)dictionary toChannel:(NSString *)channelName
{
NSParameterAssert(channelName != nil);
NSMutableDictionary *channelsDictionary = [self mh_channelsDictionary];
@synchronized (channelsDictionary)
{
NSMutableArray *listeners = [channelsDictionary objectForKey:channelName];
if (listeners != nil)
{
for (MHChannelListener *listener in listeners)
{
if (listener.object != nil)
{
if (listener.queue == nil)
listener.block(listener, dictionary);
else
dispatch_async(listener.queue, ^{ listener.block(listener, dictionary); });
}
}
[self mh_pruneDeadListenersFromChannel:channelName];
}
}
}
- (void)mh_listenOnChannel:(NSString *)channelName block:(MHChannelsBlock)block
{
[self mh_listenOnChannel:channelName priority:0 queue:nil block:block];
}
- (void)mh_listenOnChannel:(NSString *)channelName priority:(NSInteger)priority queue:(dispatch_queue_t)queue block:(MHChannelsBlock)block
{
NSParameterAssert(channelName != nil);
NSParameterAssert(block != nil);
NSMutableDictionary *channelsDictionary = [self mh_channelsDictionary];
@synchronized (channelsDictionary)
{
NSMutableArray *listeners = [channelsDictionary objectForKey:channelName];
if (listeners == nil)
{
listeners = [NSMutableArray arrayWithCapacity:2];
[channelsDictionary setObject:listeners forKey:channelName];
}
MHChannelListener *listener = [[MHChannelListener alloc] init];
listener.object = self;
listener.block = block;
listener.priority = priority;
listener.queue = queue;
[listeners addObject:listener];
[self mh_pruneDeadListenersFromChannel:channelName];
[listeners sortUsingComparator:^(MHChannelListener *obj1, MHChannelListener *obj2)
{
if (obj1.priority < obj2.priority)
return NSOrderedDescending;
else if (obj1.priority > obj2.priority)
return NSOrderedAscending;
else
return NSOrderedSame;
}];
}
}
- (void)mh_removeFromChannel:(NSString *)channelName
{
NSParameterAssert(channelName != nil);
NSMutableDictionary *channelsDictionary = [self mh_channelsDictionary];
@synchronized (channelsDictionary)
{
NSMutableArray *listeners = [channelsDictionary objectForKey:channelName];
if (listeners != nil)
{
for (MHChannelListener *listener in listeners)
{
if (listener.object == self)
listener.object = nil;
}
[self mh_pruneDeadListenersFromChannel:channelName];
}
}
}
- (void)mh_debugChannels
{
NSMutableDictionary *channelsDictionary = [self mh_channelsDictionary];
@synchronized (channelsDictionary)
{
NSLog(@"Channels dictionary: %@", channelsDictionary);
}
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment