Thursday, January 15, 2009

Simple HOM

While it is good to see that Higher Order Messaging is still inspiring new work, I feel a bit guilty that part of that inspiration are sentiments such as the following:

"Still I have yet to find a simple implementation that I like and that does not use private methods. The last thing I want is a relying on classes which can break at any time."
Mea culpa.

While I did explain a bit why the current HOM implementation is a bit gnarly, code probably speaks more loudly than repeated mea-culpas.

So, without further ado, a really simple HOM implementation. An NSArray category provides the interface and does the actual processing:

@interface NSArray(hom)

-collect;

@end

@implementation NSArray(hom)

-(NSArray* )collect:(NSInvocation*)anInvocation
{
  NSMutableArray *resultArray=[NSMutableArray array];
  for (id obj in self ) {
    id resultObject;
    [anInvocation invokeWithTarget:obj];
    [anInvocation getReturnValue:&resultObject];
    [resultArray addObject:resultObject];
  }
  return resultArray;
}

-collect {
  return [HOM homWithTarget:self selector:@selector(collect:)];
}

@end
The fact that NSInvocation deals with pointers to values rather than values makes this a bit longer than it needs to be, but the gist is simple enough: iterate over the array, invoke the invocation, return the result.

That leaves the actual trampoline, which is really just an implementation detail for conveniently creating NSInvocation objects.


@interface HOM : NSProxy {
  id xxTarget;
  SEL xxSelector;
}

@end

@implementation HOM

-(void)forwardInvocation:(NSInvocation*)anInvocation
{
  [xxTarget performSelector:xxSelector withObject:anInvocation];
}

-methodSignatureForSelector:(SEL)aSelector
{
  return [[xxTarget objectAtIndex:0] methodSignatureForSelector:aSelector];
}

-xxinitWithTarget:aTarget selector:(SEL)newSelector
{
  xxTarget=aTarget;
  xxSelector=newSelector;
  return self;
}

+homWithTarget:aTarget selector:(SEL)newSelector
{
  return [[[self alloc] xxinitWithTarget:aTarget selector:newSelector] autorelease];
}

@end
This code compiles without warnings, does not use any private API, and runs on both Leopard and the iPhone. The Xcode project can be downloaded here.

Labels: ,

7 Comments:

Anonymous Christian said...

Nice work.

January 15, 2009 12:14 PM  
Blogger Felix said...

Very nice stuff. Thanks a bunch!

March 31, 2009 2:43 AM  
OpenID sbwoodside said...

I was hoping to do this:

[[_buttons collect] setHidden:YES];

instead of this:

for( UIButton * b in _buttons ) { b.hidden = YES; }

A bit perverse I know but nicer syntax... anyway it crashed. Would be cool to have something like an "each" protocol method on NSArray for that purpose ;-)

April 27, 2009 7:02 PM  
Blogger Tomáš Znamenáček said...

Writing code like this:

[[array collect] stringByAppendingString:@"s"]

…results in warning for ‘initialization from distinct Objective-C type’. Is that an error on my part? It can be solved by a simple cast, but obviously I’d rather live without the cast.

May 12, 2009 12:23 AM  
Anonymous Anonymous said...

If you don't need invocations, you can always use -[NSArray makeObjectsPerformSelector] and -[NSArray makeObjectsPerformSelector:withObject:];

Probably not exactly what you are asking for, but I use it for what you are doing all the time…

michael

August 5, 2009 11:06 PM  
Blogger sbwoodside said...

Add this to your interface if you want to do nested collects on the same line:

// Get compiler to stop complaining:

@interface NSObject(hom)

-collect;

@end


Then you can do without warnings e.g.:

_buttonActions = [[[[[nodes collect] attributeForName:@"action"] collect] stringValue] retain];

September 8, 2009 10:03 PM  
Blogger Marcel Weiher said...

sbwoolside: use -do instead of -collect and you will get better results with messages that return void.

As the name implies, -collect collects the return values.

September 20, 2009 10:37 AM  

Post a Comment

<< Home