How to create instance of my own class using NSInvocation? - ios

I am trying to create a new instance of my custom class (custom init method call, with a BOOL parameter) dynamically. How can I use NSInvocation to do that?
This is what I have so far:
NSMethodSignature* signature = [NSClassFromString(className) instanceMethodSignatureForSelector: sel];
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: signature];
[invocation setTarget: [NSClassFromString(className) alloc]];
[invocation setSelector:sel];
[invocation setArgument:&value atIndex:2];
[invocation invoke];
[invocation getReturnValue:&obj];
Above sample throws error in line [invocation invoke]; error is "message sent to deallocated instance".

Your code doesn't work because the NSInvocation doesn't retain the target or any arguments unless you tell it to (with retainArguments). So, you alloc an instance and then it gets destroyed before you invoke the NSInvocation.
Alternatively, create an instance variable and store the alloc'd instance there, then pass that to the NSInvocation.

Related

ios invoke block target with copy method,but crashed

I use invocation call block copy, I think it's equals to [block copy],but crashed why?
#implementation MyService
+ (void)load {
[MyService startRequest:^(id _Nonnull responseObject, NSError * _Nonnull error) {
NSLog(#"%#",self);
}];
}
+ (void)startRequest:(void (^)(id responseObject,NSError *error))object {
SEL sel = #selector(copy);
NSMethodSignature* methodSign = [object methodSignatureForSelector:sel];
NSInvocation* invocation = [NSInvocation invocationWithMethodSignature:methodSign];
[invocation setSelector:sel];
[invocation setTarget:object];
[invocation invoke];
}
#end
Due to an undocumented feature (see this answer), invoking NSInvocation on a block actually calls the block, instead of sending a message to the block object. The method signature you provided is not the method signature of the actual block call, so it leads to undefined behavior.

NSTimer + NSInvocation causing crash on iOS 7

I'm running into a crash when using +[NSTimer scheduledTimerWithTimeInterval:invocation:repeats] on iOS 7. The code is straightforward enough; here is the copy paste (with variable renames) in its entirety.
SEL selector = #selector(callback);
NSMethodSignature *signature = [self methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:self];
[NSTimer scheduledTimerWithTimeInterval:0.5 invocation:invocation repeats:NO];
When the timer fires, my app crashes with the following stack trace:
I thought that maybe one of the variables was no longer retained (even though NSTimer's documentation mentions that it retains all referenced parameters), so I strongly retained all of the variables to self. Unfortunately, the crash persists.
Thanks in advance!
You are missing this line [self.invocation setSelector:selector];
This will work
SEL selector = #selector(callback);
NSMethodSignature *signature = [self methodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:self];
[invocation setSelector:selector];
[NSTimer scheduledTimerWithTimeInterval:0.5 invocation:invocation repeats:NO];
- (void)callback
{
NSLog(#"triggered");
}
Output:
triggered
This answer seems to suggest you need to call setSelector: on the invocation in addition to init-ing it with the signature.

NSInvocation and memory issues [duplicate]

This question already has answers here:
NSInvocation getReturnValue: called inside forwardInvocation: makes the returned object call dealloc:
(3 answers)
Closed 9 years ago.
So I come from the Java world where we are blissfully ignorant of memory management issues. For the most part, ARC has saved my butt, but here is something that has got me stumped. Basically I am using NSInvocations for some stuff, and I ran into some nasty memory issues before I made the following code modifications. Since I made these modifications, the memory crashes have gone away, but I am usually very scared of code that I dont understand. Am I doing this right?
Before: all sorts of memory issues:
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[target class] instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:target];
[invocation setArgument:&data atIndex:2];
[invocation setArgument:&arg atIndex:3];
[invocation invoke];
NSString *returnValue;
[invocation getReturnValue:&returnValue];
After : No memory issues, but I am not sure I got this right:
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[target class] instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:target];
[invocation setArgument:&data atIndex:2];
[invocation setArgument:&arg atIndex:3];
[invocation invoke];
CFTypeRef result;
[invocation getReturnValue:&result];
if (result)
CFRetain(result);
NSString *returnValue = (__bridge_transfer NSString *)result;
Edit:
I Just wanted to add on basis of the answer below, I used objc_msgSend, as such.:
NSString * returnValue = objc_msgSend(target, selector, data, arg);
And it solves all the memory issues, plus looks much simpler. Please comment if you see any issues with this.
I will answer your question like this: Don't use NSInvocation. It's just a friendly advice to avoid that if possible.
There are many nice ways to do callbacks in Objective-C, here are two that may be useful for you:
Blocks: Defined in context, choose any argument count and types, possible issues with memory too. There are many resources on how to use them.
performSelector: max 2 object arguments, invoked using:
[target performSelector:selector withObject:data withObject:args];
In addition, when I need to invoke a selector with 4 arguments I still don't use NSIvocation, but rather call objc_msgSend directly:
id returnValue = objc_msgSend(target, selector, data, /* argument1, argument2, ... */);
Simple.
Edit: With objc_msgSend you need to be careful with the return value. If your method returns an object, use the above. If it returns a primitive type, you need to cast the objc_msgSend method so the compiler knows what's going on (see this link). Here's an example for a method that takes one argument and returns a BOOL:
// Cast the objc_msgSend function to a function named BOOLMsgSend which takes one argument and has a return type of BOOL.
BOOL (*BOOLMsgSend)(id, SEL, id) = (typeof(BOOLMsgSend)) objc_msgSend;
BOOL ret = BOOLMsgSend(target, selector, arg1);
If your method returns a struct, things are a bit more complicated. You may (but not always) will need to use objc_msgSend_stret -- see here for more info.
Edit: - this line have to be added to the code, or Xcode will complain:
#import <objc/message.h>
or
#import ObjectiveC.message;
You should generally consider blocks as a superior alternative where possible (they succeeded NSInvocation).
As far as the return value, you can use this:
CFTypeRef result = NULL;
[invocation getReturnValue:&result];
NSString *returnValue = (__bridge NSString *)result;
The underlying issue here is that -getReturnValue: does not return an out object, as far as ARC is concerned. Therefore, it is likely getting the reference count operations wrong (the compiler adds these for you in ARC), because -getReturnValue:'s parameter is void*, not an out object (e.g. NSObject**).

Perform selector warning vs dispatch _async

[NOT DUPLICATE: read well the question and the already given answers, I've already read them]
I'm facing that problem, I need to substitute -performSelector method since it causes that warning in the compiler with ARC
performSelector may cause a leak because its selector is unknown
I'm aware that there different questions about that topic:
One
Two
Three
and I'm also aware about the techniques to avoid that warning.
Sometimes as a solution I found that the most suggested advice is to use dispatch_async(dispatch_get_main_queue(),^(void) { WHATEVER });
but as far as I know dispatching would require the execution of the block in the next run loop, -performSelector (without delay) is executed immediately.
Why this mental masturbation? Imagine that you are using the new Gamekit method for authentication, game kit could send you inside the auth block a view controller to make the user do some operation (such as creating the id, log in, etc). It could be useful to warn the user if he/She wants to see that view controller. To do that and other stuffs I'm writing a protocol. In particular that method should return a BOOL - (BOOL)shouldLoadGameKitViewController: (UIViewController*) viewController;, I'd like to call it using -performSelector. Here is the point, if the method dosen't return immediately I can't get the answer from the delegate.
I'm using NSInvocation to make that happen but is verbose, does exist some other way?
[UPDATE WITH CODE] Pay attention that now I'm using invocation, the thread-check part is not still implemented. Commented there is the part that gave the warning
- (void) dispatchToDelegate: (SEL) selector withArg: (id) arg error: (NSError*) err
{
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setTarget:self.delegate];
if([self.delegate respondsToSelector: selector])
{
if(arg != NULL) {
[invocation setArgument:&arg atIndex:2];
[invocation setArgument:&err atIndex:3];
// [_delegate performSelector: selector withObject: arg withObject: err];
}else {
[invocation setArgument:&err atIndex:2];
// [_delegate performSelector: selector withObject: err];
}
[invocation invoke];
}
else
DLog(#"Method not implemented in the delegate");
}
[SORT OF SOLUTION STILL UNTESTED]
- (BOOL) dispatchToDelegate: (SEL) selector withArg: (id) arg error: (NSError*) err
{
NSMethodSignature *methodSig = [[self class] instanceMethodSignatureForSelector:selector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setTarget:self.delegate];
BOOL result = NO;
if([self.delegate respondsToSelector: selector])
{
if(arg != NULL) {
[invocation setArgument:&arg atIndex:2];
[invocation setArgument:&err atIndex:3];
// [_delegate performSelector: selector withObject: arg withObject: err];
}else {
[invocation setArgument:&err atIndex:2];
// [_delegate performSelector: selector withObject: err];
}
if ([NSThread isMainThread]) {
[invocation invoke];
}
else{
[invocation performSelectorOnMainThread:#selector(invoke) withObject:nil waitUntilDone:YES];
}
[invocation getReturnValue:&result];
}
else
NSLog(#"Missed Method");
return result;
}
Using the NSMethodSignature method, is possible to gear up, and ask for the return type. I still didn't test but it should made the trick.
Try to make this to avoid leak or "unknown selector send to instance" issue:
SEL selector = NSSelectorFromString(#"methodName::");
if ([obj respondsToSelector:selector])
[obj performSelector:selector withObject#[args,array]];
You can use dispatch_sync instead of dispatch_async to have the block performed immediately (your code will be blocked until the block returns).
Another alternative is to suppress the warning temporarily, as explained in this answer.

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

Resources