CocoaDev

Edit AllPages

CCDMessageDistributer is a TrampolineObject which started as a (premature?) refactoring of BSTrampoline by ThomasCastiglione. You might well be better off with BSTrampoline or LSTrampoline instead of CCDMessageDistributer in terms of performance. MPWFoundation also has a nice implementation, but I’m not sure about IMP caching.

CCDMessageDistributer implements HigherOrderMessaging (all, collect, select, reject, combine) for objects that responds to objectEnumerator, such as NSArray, NSSet, NSDictionary. objects that respond to keyEnumerator get treated as keyed collections too :).

If you want to use CCDMessageDistributer, use it like this:

// sends message to all elements in myArray myArray all] myMessage:arg1 withObject:arg2];

// sends message to all elements in myDictionary, and return result collection. [[myDictionary collect] myMessage:arg1 withObject:arg2];

// sends message to all elements in mySet, keeps those that returned YES [[mySet select] testMethod];

// concatenate (string) elements in mySet [[mySet combine] stringByAppendingString:nil];

You can also do things like this:

id distributer = [myCollection collect]; id collection1 = [distributer myMessage:arg1 withObject:arg2]; id collection2 = [distributer anotherMessage:arg];

The discussion page is [[MessageDistribution. All thoughts or advice or criticism welcomed.

This is distributed under GNU. Crediting me if you use it in your own work is appreciated. If you do extend this class, please put a brief link on this page.

Also a plea: It is in your interest to fight software patents, whether you are an individual or a company!! See the electronic frontier foundation website. also http://petition.eurolinux.org/index_html?LANG=en

There may be errors since this changed to the CCD prefix. please mention them here.

Peace, Love and Truth, MikeAmy


CCDMessageDistributer.h

/* CCDMessageDistributer.h Copyright (C) 2004 Michael Amy

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */

#import “Foundation/Foundation.h”

@interface NSObject (HigherOrderMessaging)

// mutableCopy doesn’t work on a Class object, unfortunately @interface NSObject (MutableAware)

@interface CCDCollectionMediator : NSObject { @protected id element; NSEnumerator *enumerator; }

@interface CCDKeyed : CCDCollectionMediator { @protected id key, collection; } @end

@interface CCDForwarder : NSProxy { @protected id recipient; }

@interface CCDMessageDistributer : NSObject { @protected id collection, mediator, target; }

@end

@interface CCDCollect : CCDMessageDistributer { @protected id resultCollection, value; } @end

@interface CCDSelect : CCDCollect { @protected BOOL keep; }

@interface CCDReject : CCDSelect @end

@interface CCDReverseCombine : CCDMessageDistributer { @protected id result; } @end

@interface CCDCombine : CCDReverseCombine @end


CCDMessageDistributer.m

/* CCDMessageDistributer.m Copyright (C) 2004 Michael Amy

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */

#import “CCDMessageDistributer.h” #import “objc/objc-class.h”

static id nothing = nil; void inline replaces(id old,id new) { [new retain]; [old release]; *old = new; }

@implementation CCDCollectionMediator : NSObject

@end

@implementation CCDKeyed : CCDCollectionMediator

@implementation CCDForwarder : NSProxy

@end

@implementation CCDMessageDistributer : NSObject

@implementation [[CCDCollect : CCDMessageDistributer

@implementation [[CCDSelect : CCDCollect

@implementation CCDReject : CCDSelect

@implementation CCDReverseCombine : CCDMessageDistributer

@implementation CCDCombine : CCDReverseCombine

@implementation NSArray (MutableAware)

@implementation NSDictionary (MutableAware)

@implementation NSSet (MutableAware)

@implementation NSObject (HigherOrderMessaging)

@end


You can use this for testing:

#import <Foundation/Foundation.h> #import “CCDMessageDistributer.h”

void logElementsOf(id collection, NSString *operation) { NSLog(@”Testing %@;”,operation); NSString *index; NSEnumerator *enumerator = [collection objectEnumerator]; if (enumerator == nil) { NSLog(@”Enumerator is missing - did the invocation really return an object?”); } while (index = [enumerator nextObject]) { NSLog(index); } }

void test(id collection) { logElementsOf(collection,@”initial elements are”); collection all] appendString:@””]; logElementsOf(collection,@”all”); logElementsOf([[collection collect] stringByAppendingString:@””],@”collect”); logElementsOf([[collection select] hasPrefix:@”a”],@”select”); logElementsOf([[collection reject] hasPrefix:@”a”],@”reject”); [[NSLog(@”Testing combine; %@”, collection combine] stringByAppendingString:nil]); [[NSLog(@”Testing combine into >; %@”, collection combine] stringByAppendingString:[[[NSMutableString stringWithString:@”>”]]); NSLog(@”Testing reverse combine; %@”, collection reverseCombine] stringByAppendingString:nil]); [[NSLog(@”Testing reverse combine into >; %@”, collection reverseCombine] stringByAppendingString:[[[NSMutableString stringWithString:@”>”]]); }

int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

NSMutableString 
*a = [NSMutableString stringWithString:@"a"], 
*b = [NSMutableString stringWithString:@"b"], 
*c = [NSMutableString stringWithString:@"c"];

NSLog(@"------- NSArray -------");
NSArray *array = [[NSArray alloc] initWithObjects:a, b, c, nil];
test(array);

NSLog(@"------- NSDictionary -------");
NSDictionary *dict = [[NSDictionary alloc] initWithObjects:array forKeys:array];
test(dict);

NSLog(@"------- NSSet -------");
NSSet *set = [[NSSet alloc] initWithObjects:a, b, c, nil];
test(set);

[array release];
[pool release];
return 0; }

This produces:

——- NSArray ——- Testing: initial elements are a b c Testing: all a_ b_ c_ Testing: collect a__ b__ c__ Testing: select a_ Testing: reject b_ c_ Testing combine; a_b_c_ Testing combine into >; >a_b_c_ Testing reverse combine; c_b_a_ Testing reverse combine into >; c_b_a_> ——- NSDictionary ——- Testing: initial elements are c_ a_ b_ Testing: all c__ a__ b__ Testing: collect c___ a___ b___ Testing: select a__ Testing: reject c__ b__ Testing combine; c__a__b__ Testing combine into >; >c__a__b__ Testing reverse combine; b__a__c__ Testing reverse combine into >; b__a__c__> ——- NSSet ——- Testing: initial elements are c__ a__ b__ Testing: all c___ a___ b___ Testing: collect c____ a____ b____ Testing: select a___ Testing: reject c___ b___ Testing combine; c__ab_ Testing combine into >; >c__ab_ Testing reverse combine; b__ac_ Testing reverse combine into >; b__ac_>

If you need to know how it works, read on

Classes and their roles

Supporting classes are CCDForwarder and CCDCollectionMediator. Subclasses are CCDCollect, CCDSelect, CCDReject and CCDCombine|CCDReverseCombine.

CCDMessageDistributer coordinates the process, sending messages to all elements in the collection. CCDCollect collects the results, and returns them as the result of the invocation. CCDSelect keeps elements which returned YES. CCDReject keeps elements which returned NO. CCDCombine and CCDReverseCombine combine results into a single value. CCDCollectionMediator knows how to access elements of a collection. There is a subclass for keyed collections. CCDForwarder decouples forwarding from messaging. This means CCDMessageDistributer inherits any NSObject categories.

It “should” be fairly trivial to write a subclass, for example you could change:

*The way the CCDMessageDistributer works. How about a category to take advantage of multiple processors? *The kind of collection - could extend to trees etc., any other properties of the collection. *The kind of result collection returned - could take a dictionary but return an array etc. Not sure if that is useful though.

Contrasting with BSTrampoline, “do” is now “all” because reusing keywords seems a bit messy and “all” seems right, semantically. If enough people complain I’ll change it back. But I like “all”.

Extending

To write a subclass, override any or all of the methods below:

If you want to do something with the invocation, override: *prepare: - before any messages are sent. *perform: - sending of each method. You should setup (if necessary) and invoke the method. *conclude: - used to pass back the result collection in the invocation, or do any concluding tasks. *prototypeMethod - an example of the type of method handled. *forwardInvocation: - to alter the way the CCDMessageDistributer works, eg for SMP

You should also add a category to NSObject with a method to create your messager with a collection.

Notes on Combine

(NB: combine reverseCombine are comparable to foldl foldr in Haskell )

CCDCombine combines the elements in turn, using the method specified, to produce a single result of the same type as the elements in the collection. CCDCombine therefore only accepts methods that take an object as argument and return another object which also responds to the same method.

For example, stringByAppendingString: which takes a string as target and argument, and returns a string. Using combine and stringByAppendingString we can append all strings in a collection thusly:

myStrings combine] stringByAppendingString:nil];

nil is used because that argument will be replaced using elements from the collection. If an argument is specified, it will be used before the first element. This allows you to “carry on” a calculation over several collections. Make sure the argument can respond meaningfully to the selector - this may mean it needs to be mutable. For example:

[[myStrings combine] stringByAppendingString:[[[NSMutableString stringWithString:@”> “]];

joins all the strings in the collection onto “> “

Any other arguments to the method are left untouched, and used every time.

Take a really simple example - summing NSNumbers. We have a method called plus:, denoted “+”. [number plus:anotherNumber] returns the sum of the two numbers as an NSNumber. Our collection contains 5 elements, each of which is an NSNumber:

[@1,@2,@3,@4,@5]

This is a simplified representation of what will happen:

[@1 +:@2] the 1st and 2nd NSNumbers are plus:ed and stored in the result, denoted @, | [@ +:@3] the result is plus:ed with the 3rd NSNumber, | [@ +:@4] that result is plus:ed with the 4th NSNumber, | [@ +:@5] and so on, | v result the final result is returned as the returnValue of the original invocation.

Combine uses the previous result as a target, with the next element as first argument. Reverse combine does things the opposite way round, with the result as argument and the next element as target, which may affect the final result depending on the method used. If your result comes out in reverse, use the opposite version.


We should put together some numbers comparing e.g. performance between LSTrampoline, BSTrampoline, and this.

The numbers would be mostly for my own benefit in deciding whether to continue using LSTrampoline (now CXLTrampoline) or to move to CCDMessageDistributor. (Probably I’ll stick with the CXL one cos of the GPL, but I’m impressed with the whole combine/reverse-combine thing, and I know I’d not have thought to package specific HOM operations as objects in MediatorPattern.)

– RobRix

Yeah I should have put up a comparison already. I do think that it was presumptious of me to use the CCD prefix for cocoadev. In fact thinking about it, that’s more likely to cause a namespace conflict than anything else. And who decides what goes under CCD? So maybe I should move it yet again : ( and engage brain before clicking mouse next time.

We could do a quick, time-based comparison using the test routines, but I’m pretty sure CCDMessageDistributer will come off worse, because of the extra message unraveling involved, and probably come off slightly worse memory wise because of the extra objects created. But these are constant and O(1) complexity differences, so for me, I don’t worry, I buy a better machine. The point about CCDMessageDistributer is it should be easy to extend with new HOM operations. – MikeAmy

My expectation would be that the numbers would read slightly in favour of [BL]STrampoline, but like you, I don’t worry; it’s just a matter of ‘fast enough’– in (for instance) LodeStone, I use HOM (a lot) in the generating of bezier patches, so there are potentially thousands of message-sends to said patch when I change it. Right now, it’s fast enough; as long as it’s fast enough with *MessageDistributor too, there’s no problem for me in changing.