Question

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.

Was it helpful?

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.

OTHER TIPS

The accepted answer by edelaney05 was great, but I think it needs a slight tweak for ARC. (I can't add comments yet, so creating a new answer to document what I found)

As is, I got the compile error: "Pointer to non-const type 'NSError *' with no explicit ownership"

I researched this and found that I needed:

 NSError * __autoreleasing error = nil;
 NSError * __autoreleasing *errorPointer = &error;

References that led me to this answer:

NSInvocation & NSError - __autoreleasing & memory crasher

Automatic Reference Counting: Pointer to non-const type 'NSError *' with no explicit ownership

http://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html

"__autoreleasing is used to denote arguments that are passed by reference (id *) and are autoreleased on return."

The parameter type is NSError **, which you get from taking the address of an NSError * that you want the error to be written to. To set an argument in an NSInvocation, you need to pass the address of a value of the argument to setArgument:, so you need to put your NSError ** in a variable (I call it errPointer here), and take the address of that (which will be an NSError ***) to pass to setArgument:. You don't need the errPointer variable afterwards.

NSError *err = nil;
NSError **errPointer = &err;
[invocation setArgument:&errPointer atIndex:4];
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top