Re: Abstracting a group of commonly named Selectors


Sandor Szatmari
 

Jeff,

Works great!  See my implementation below…
The one thing I wasn’t sure about was the types array argument for class_addMethod() The fourth param…
I used ‘i’ to specify the return type of integer.  The return type is an enum.  Wasn’t sure if there was a better choice
This removed a ton of unnecessarily redundant code! 

-(id)init
{
self = [super init];
if ( self != nil )
{
IMP parser_imp = class_getMethodImplementation([self class], @selector(parseStep) );
for ( SelectorStep *step in [self parseSteps] )
{
SEL sel = step.selector;
if ( [self respondsToSelector:sel] == NO )
class_addMethod( [self class], sel, parser_imp, "i@:" );
}
}
return self;
}

// --------------------------------------------

-(ParserStepStatus)parseStep
{
const char *selName = sel_getName(_cmd);
NSString *table = [NSString stringWithCString:selName
       encoding:NSUTF8StringEncoding];
if ( [table isEqualToString:@"parseRoutePartitions"] )
NSLog( @"Stop" );
NSAssert1( [table hasPrefix:@"parse"], @"Selector must start with parse. Bad Selector: '%@'", table );
table = [[table substringFromIndex:5] snakeCase];
return [self parseStepByNameWithParams:[self stepDictForTableName:table]];
}

Sandor

On Jul 21, 2020, at 19:56, Sandor Szatmari via groups.io <admin.szatmari.net@...> wrote:

Jeff,

So what you’re suggesting is…

1. Define a template implementation
2. Associate each selector pattern with that implementation

That seems like it should work.  I’ll give it a try and let you know how it goes…

Thanks,
Sandor

On Jul 21, 2020, at 17:10, Jeff Laing <jefflaing@...> wrote:



In the designated initialise of your Parser class, you can use class_addMethod to add whatever you like to your instance's class.

https://developer.apple.com/documentation/objectivec/1418901-class_addmethod?language=objc

The method implementation gets called with the selector as its second argument and you can cut the string up at runtime to then call your Parse. Here's a snippet from an app I did in the past that wanted to dynamically adjust to any entries that were added to a popup menu in Interface Builder. It iterates over the popup, and for every entry present, looks for then adds a dynamic method called _popup.

        IMP popup_imp = class_getMethodImplementation([self class], @selector(_popup00:));
...
        for (NSArray *item in popup) {
                // make sure that we implement the required method
                SEL sel = NSSelectorFromString([NSString stringWithFormat:@"_popup%02ld:",(long)[menuItems count]]);
                if (![self respondsToSelector:sel]) {
                        class_addMethod([self class], sel, popup_imp, "v@:@");  // -(void)method:(id)argument;
                }

The implementation of the method gets the name of the selector using set_getName, then punts across to the actual code.

- (void)_popup00:(id)sender {
//          01234567
        const char *name = sel_getName(_cmd);
        const int idx = atoi(name+6);
        id resp = [self.popupResponders objectAtIndex:idx];
        [resp callWithArrayOfArgs:self.popupArguments];
}

Extracting your specific token from the message is just name+. This example is using (void) but it should work just fine - you aren't using the "no such method" path, you are just binding the method really late in the compile/link/run process...

Join {cocoa@apple-dev.groups.io to automatically receive all group messages.