icleversoft
1/5/2013 - 1:02 PM

NSObject+MHChannels.h

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.


#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

#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