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)



@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:)];

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;


@implementation HOM

  [xxTarget performSelector:xxSelector withObject:anInvocation];

  return [[xxTarget objectAtIndex:0] methodSignatureForSelector:aSelector];

-xxinitWithTarget:aTarget selector:(SEL)newSelector
  return self;

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

This code compiles without warnings, does not use any private API, and runs on both Leopard and the iPhone. Github:

EDIT (Aug 15 2015): Changed SimpleHOM download link to github repo.


Christian said...

Nice work.

Felix said...

Very nice stuff. Thanks a bunch!

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 ;-)

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.

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…


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)



Then you can do without warnings e.g.:

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

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.