Flexible method calls with NSInvocation

I was recently working on a means to perform concurrent saves (for performance reasons) of some records to Parse .     As I worked through this I realized that there may be some benefit in creating a generic dispatcher that wasn’t tied to a particular source of those records.   My goal was to give this generic dispatcher the means to retrieve a total record count and set of records in a range, without having to know the details of where the count and records came from.

The dispatcher would also be given parameters that told it how many records to save at once (Parse has a saveAll feature for persisting objects to the cloud), along with how many saves to perform concurrently.

Selectors

At first I explored using one or more selectors that I passed from my custom record save method to the generic dispatcher.     I quickly ran into the warning “performSelector may cause a leak because its selector is unknown”.  I found an excellent post on Stack Overflow that discusses this issue.  One proposed explanation for this warning was that since the selector is unknown to the compiler, ARC cannot enforce proper memory management.

The post included some workarounds including the use of the pragma:

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

along with some suggestions on how to use this with a macro to restrict its scope.

One of the comments suggested using NSInvocation as an alternative to using performSelector.

NSInvocation

According to the Apple documentation on NSInvocation:

“An NSInvocation object contains all the elements of an Objective-C message: a target, a selector, arguments, and the return value. Each of these elements can be set directly, and the return value is set automatically when the NSInvocation object is dispatched.”

As opposed to just using a selector by itself (with performSelector), an NSInvocation allows one to bundle up arguments that the target of the selector might need to perform its tasks, providing further abstraction.

Getting a record count

An invocation was perfect for my needs.   Let’s take a look at how to create the invocation that returns a count of all the records:


// My method to save records to Parse
-(void)recordSave
{
    // Create an invocation that will allow the generic dispatcher to determine the total
    // number of objects to save.

    // Determine the total number of records we wish to save.
    NSNumber *countNum = ...;

    // Associate the invocation with the method getCount:
    SEL getCountSelector = @selector(getCount:);
    NSMethodSignature *getCountSignature = [ParseConnection instanceMethodSignatureForSelector:getCountSelector];

    // Create an invocation named getCountInvocation
    NSInvocation *getCountInvocation = [NSInvocation invocationWithMethodSignature:getCountSignature];
    [getCountInvocation setSelector:getCountSelector];
    [getCountInvocation setTarget:self];

    // Setup the first argument (starts at index=2) for the invocation call to getCount:
    [getCountInvocation setArgument:&countNum atIndex:2];

    // Call our dispatcher with getCountInvocation.
    // We'll talk about getRecordsInvocation later.
    NSDictionary *statusDict = [self saveDispatcher:getRecordsInvocation withGetCountInvocation:getCountInvocation];
}

// This routine doesn't do anything other than return its input.  The important thing
// is that the method name is buried in the construction of the invocation.  When the dispatcher
// gets the invocation from recordSave, it doesn't need to know anything about "getCount".
- (NSNumber *)getCount:(NSNumber *)countNum
{
    return countNum;
}

// Our generic save dispatcher
- (NSDictionary *)saveDispatcher:(NSInvocation *)getRecordsInvocation
          withGetCountInvocation:(NSInvocation *)getCountInvocation
{
    // How many records do we need to save?  Use getCountInvocation to retrieve this information.
    NSUInteger objectCount;
    NSNumber *objectCountNum;

    // Note that this dispatcher doesn't know anything about getCount:  It also doesn't need to supply any
    // input arguments since they are already baked into the invocation.
    [getCountInvocation invoke];

    // Method invoked with getCountInvocation returns an NSUInteger wrapper in a NSNumber
    [getCountInvocation getReturnValue:&objectCountNum];

    objectCount = [objectCountNum unsignedIntegerValue];

    ...
}

An invocation can be modified

The getCountInvocation invocation was pretty straightforward.  Creating and using getRecordsInvocation is a little more involved, but also illustrates the flexibility of invocations.


-(void)recordSave
{

    ...

    // Our array of record ids.  We'll bundle this in the invocation.
    NSArray *recordIdArr = ...;

    // Create an invocation that will allow saveDispatcher:withGetCountInvocation: to fetch objects to save.
    //
    // The invocation will pass an array of record ids to the method allocGetRecords:startingAtIndex:endingAtIndex:
    // as the first argument.   This gets baked into the invocation and the dispatcher doesn't need to know about it.
    //
    // The second and third arguments will be added by saveDispatcher:withGetCountInvocation: as the starting and
    // ending index, prior to executing the invocation.
    //

    // Associate the invocation with the method allocGetRecords:startingAtIndex:endingAtIndex:
    SEL getObjectsSelector = @selector(allocGetRecords:startingAtIndex:endingAtIndex:);
    NSMethodSignature *getObjectsSignature = [ParseConnection instanceMethodSignatureForSelector:getObjectsSelector];

    // Create an invocation named getRecordsInvocation
    NSInvocation *getRecordsInvocation = [NSInvocation invocationWithMethodSignature:getObjectsSignature];
    [getRecordsInvocation setSelector:getObjectsSelector];
    [getRecordsInvocation setTarget:self];

    // Setup the first argument (starts at index=2) for the invocation call to allocGetRecords:startingAtIndex:endingAtIndex:
    [getRecordsInvocation setArgument:&recordIdArr atIndex:2];

   ...
}

// Method to fetch records in a given range.
- (NSArray *)allocGetRecords:(NSArray *)recordIdArr
                   startingAtIndex:(NSNumber *)startingIndexNum
                     endingAtIndex:(NSNumber *)lastIndexNum
{
    // Perform some fetches from Core Data to get the records requested.
    NSArray *recordArr = ...;
    return recordArr;
}

- (NSDictionary *)saveDispatcher:(NSInvocation *)getRecordsInvocation
          withGetCountInvocation:(NSInvocation *)getCountInvocation
{
    ...

    // An array to hold the records we fetch
     NSArray *recordArr;

    // The invocation getRecordsInvocation expects the dispatcher to supply starting and last indices
    // as second and third arguments.
    NSNumber *startingIndexNum = ...;
    NSNumber *lastIndexNum = ...;

    // Note that this dispatcher doesn't know anything about allocGetRecords:startingAtIndex:endingAtIndex:
    // It also doesn't need to supply recordIdArr to that method, since it is already baked into the invocation.
    //
    // We are responsible however for supplying a range of records to retrieve to the invocation.
    [getRecordsInvocation setArgument:&startingIndexNum atIndex:3]; // Second arg
    [getRecordsInvocation setArgument:&lastIndexNum atIndex:4]; // Third arg

    [getRecordsInvocation invoke];
    [getRecordsInvocation getReturnValue:&recordArr];

}

Our get records method name starts with “alloc”

Note that we named our get records method allocGetRecords:startingAtIndex:endingAtIndex:

Why the prefix “alloc”?  When the caller (our dispatcher) is getting the return result as an object (in our case an NSArray), it needs to retain the return result.  I found that I got an exception when I failed to do this.

You can actually use any of the prefixes alloc, new, copy or mutableCopy as discussed in the documentation section Basic Memory Management Rules.

Calling our invocation multiple times

NSInvocation is not thread safe as mentioned in the Threading Programming Guide.   We need to call a given invocation from only one thread at a time.  It makes sense that this restriction is in place since the input arguments can be changed and the result is obtained with the NSInvocation getReturnValue: method call.

If you do need to call a given invocation from multiple threads, you can synchronize access to it in a similar fashion to what I showed in my blog post Singletons and synchronized code.

Here is an example:


    // Need to serialize access to this invocation since we are altering arguments and getting different
    // return results.
    id parseSaveAllInvocationLockObj = [[CommonStateSingleton sharedCommonStateSingleton] parseSaveAllInvocationLockObj];

    @synchronized(parseSaveAllInvocationLockObj){
        [getObjectsInvocation setArgument:&startingIndexNum atIndex:3]; // Second arg
        [getObjectsInvocation setArgument:&lastIndexNum atIndex:4]; // Third arg

        [getObjectsInvocation invoke];
        [getObjectsInvocation getReturnValue:&objectInfoDict];
    }

Advertisements

One response to “Flexible method calls with NSInvocation

  1. Pingback: The development of Contacts2Web | Finalize.com: My journey with iOS and other code adventures

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s