NSInvocation and memory issues [duplicate]
Asked Answered
S

2

8

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.

Sociolinguistics answered 7/7, 2013 at 6:1 Comment(2)
That's unlikely to be the right way to fix things, but what's the selector, and what, specifically, were the memory issues?Londonderry
The selector is a simple method on the target class. Takes 2 objects and returns a string. The issues varied - mostly bad access.Sociolinguistics
F
5

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;
Fuller answered 7/7, 2013 at 7:16 Comment(23)
I do know about blocks, but not about objc_msgSend. How is objc_msgSend better than NSInvocation?Sociolinguistics
@Sociolinguistics Less code, simplier, no memory issues. objc_msgSend is how Objective-C methods are implemented. Every method call [target selector:arg] is translated to objc_msgSend(target, selector, arg)Fuller
@iMartin: objc_msgSend() or objc_msgSend_fpret() or objc_msgSend_stret() ...Alethaalethea
@MartinR He is expecting an object, so objc_msgSend. but in this case, simple performSelector is enough (2 object arguments, object return value)Fuller
Ok - I tried out objc_msgSend - seems to cause no issues, but looks too good to be true... Why does NSInvocation exist if objc_msgSend is so much simpler to use, I dont know.Sociolinguistics
I should add, although in this case I just have 2 arguments, I will need to use the same mechanism for more.Sociolinguistics
@Sociolinguistics Because you can store single NSInvocation instead of separate arguments. Because you can build arguments dynamically (e.g. loop), while with this objc_msgSend you had to provide fixed number of arguments.Fuller
Cool - so are there any caveats whith objc_msgSend? I am just concerened it is so much easier to do than NSInvocation, that entire code I posted up there changed to this: NSString * returnValue = objc_msgSend(target, selector, data, arg, cell); Am I going to regret this?Sociolinguistics
@Sociolinguistics I don't know about any caveats. The advantage of NSInvocation is that it is an object. So can be build and stored dynamically. This objc_msgSend is just a C function. I edited the answer to mention non-object return values.Fuller
I agree that blocks are a much better alternative. I would not recommend objc_msgSend() because that is a runtime function and you have to know about the various return types. The CAVEAT about objc_msgSend() and also performSelector is that the ARC compiler does not know the ownership semantics of the returned object (whether it is a +1-retained object or not).Alethaalethea
@MartinR But this applies only for methods like -init or +new. “Normal” methods follow usual memory semantics and return autoreleased object. Or I am missing something? (In addition, NSInvocation has the same problem)Fuller
@iMartin: It is alloc (not init!), new, copy and mutableCopy. And yes, NSInvocation has the same problem. My point was mainly that blocks are the better solution.Alethaalethea
objc_msgSend (or objc_msgSend_stret) needs to be cast to the right function pointer type before calling itProtist
@Protist No, it doesn't need to be casted. The signature is id self, SEL _cmd, ..., so it accepts any arguments you provide. Casting may prevent some wrong invocations, but it's not necessary.Fuller
@Protist is right, iMartin; if you're going to use objc_msgSend() you should always cast it: red-sweater.com/blog/320/abusing-objective-c-with-class and seel also code.google.com/p/rococoa/wiki/ObjcMsgSendLondonderry
Further, your use of objc_msgSend_stret() for an int return value is wrong. The plain objc_msgSend() should be used for any non-floating point return value that can fit into a pointer. The _stret version is only for a struct return that needs more space than one register on a particular architecture. See [objc explain]: objc_msgSend_stret.Londonderry
@JoshCaswell Thank you for links, interesting reading. Feel free to update the answer if you want.Fuller
@iMartin I edited your answer to incorporate the above comments around casting objc_msgSend and when to use obcj_msgSend_stret (as I almost missed them myself).Tushy
@Tushy Thanks! Few days ago I kicked myself in ass because I didn't cast objc_msgSend and it was crashing the app on ARMv8.Fuller
That answer is perfect!!!!!! I have added the one line of code that was missing, to stop Xcode whining.Saenz
@RubberDuck Thanks, I've added alternative (modern) way of importing. Also, I changed casting of function pointer to use typeof().Fuller
@iMartin - can you explain that change?Saenz
@RubberDuck typeof(x) is a compiler feature that is substituted by real type of x. This way you don't have to repeat the types BOOL (*)(id, SEL, id) twice on the same line.Fuller
U
4

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**).

Unpaged answered 7/7, 2013 at 7:6 Comment(3)
Compare rob mayoff's answer to the "possible duplicate": __unsafe_unretained NSString *result seems to be an elegant solution.Alethaalethea
@MartinR right, that solution is also good +1. thanksUnpaged
Thanks guys - apparently using obj_msgSend like in the answer above solved all my problems, that too with 1 line of code.Sociolinguistics

© 2022 - 2024 — McMap. All rights reserved.