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. Github: https://github.com/mpw/HOM/.


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

7 comments:

  1. Very nice stuff. Thanks a bunch!

    ReplyDelete
  2. 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 ;-)

    ReplyDelete
  3. 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.

    ReplyDelete
  4. 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

    ReplyDelete
  5. 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];

    ReplyDelete
  6. 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.

    ReplyDelete