Search code examples
ioscocoa-touchautomatic-ref-countingnserrornsinvocation

How to create an NSInvocation object using a method that takes a pointer to an object as an argument


I would like to create an NSInvocation object using a method that takes a pointer to an NSError object as an argument. An example of this would be the method -

- (BOOL)writeToFile:(NSString *)path options:(NSDataWritingOptions)mask error:(NSError **)errorPtr

I undersand that I would set my invocation up like this

NSData *myData = [[NSData alloc] init];

SEL writeToFileSelector = @selector(writeToFile:options:error:);
NSMethodSignature *signature = [NSData instanceMethodSignatureForSelector:writeToFileSelector];

NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:myData];
[invocation setSelector:writeToFileSelector];

NSString *string = [NSString stringWithFormat:@"long cat"];
NSDataWritingOptions *dataOptions;
*dataOptions = NSDataWritingFileProtectionComplete;

[invocation setArgument:&string atIndex:2];
[invocation setArgument:&dataOptions atIndex:3];

For writeToFile:Options:Error: the last argument is expecting to receive a pointer instead of an object. As a result doing the following does not work -

NSError *err = nil;
[invocation setArgument:&err atIndex:4];

It seems logical that the solution might be to create a pointer to a pointer, but this causes a compiler warning. I am not sure how to execute that properly and not create a memory management problem.


Solution

  • You create the argument just the same as any other argument you'd pass to the method.

    As you point out, the method signature wants an NSError ** for its last argument (index 4). So, you will need to declare one, but there's a bit of a gotcha.

    NSError **errorPointer

    Gives you a variable that points to an NSError variable. But, since you haven't told it to point to any variable, it points to nil. Therefore when you fire the invocation the selector won't be able to change the variable your error pointer points to. In other words, it would be like calling [myData writeToFile:string options:dataOptions error:NULL].

    So, you'll want to also declare an NSError variable, and assign its address as the variable your errorPointer should point to:

    NSError *error;
    NSError **errorPointer = &error;
    

    Now you can pass in the errorPointer as an argument, and you'll be able to inspect it later if there was a problem when you invoked the method. Check out this post on NSInvocation for a little more help (hat tip to Mark Dalrymple for pointing out the blog post)

    It is important to also realize that the scope should be considered for the arguments you create and pass into your invocation. Take a look at a similar question I asked here.