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.

user2453876
  • 303
  • 2
  • 12
  • 2
    That's unlikely to be the right way to fix things, but what's the selector, and what, specifically, were the memory issues? – jscs Jul 07 '13 at 06:15
  • The selector is a simple method on the target class. Takes 2 objects and returns a string. The issues varied - mostly bad access. – user2453876 Jul 07 '13 at 06:46

2 Answers2

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;
Tricertops
  • 8,492
  • 1
  • 39
  • 41
  • I do know about blocks, but not about objc_msgSend. How is objc_msgSend better than NSInvocation? – user2453876 Jul 07 '13 at 07:19
  • @user2453876 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)` – Tricertops Jul 07 '13 at 07:21
  • @iMartin: objc_msgSend() or objc_msgSend_fpret() or objc_msgSend_stret() ... – Martin R Jul 07 '13 at 07:27
  • @MartinR He is expecting an object, so `objc_msgSend`. but in this case, simple `performSelector` is enough (2 object arguments, object return value) – Tricertops Jul 07 '13 at 07:29
  • 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. – user2453876 Jul 07 '13 at 07:32
  • I should add, although in this case I just have 2 arguments, I will need to use the same mechanism for more. – user2453876 Jul 07 '13 at 07:32
  • @user2453876 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. – Tricertops Jul 07 '13 at 07:34
  • 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? – user2453876 Jul 07 '13 at 07:35
  • @user2453876 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. – Tricertops Jul 07 '13 at 07:43
  • 1
    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). – Martin R Jul 07 '13 at 08:04
  • @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) – Tricertops Jul 07 '13 at 09:17
  • @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. – Martin R Jul 07 '13 at 09:25
  • 1
    `objc_msgSend` (or `objc_msgSend_stret`) needs to be cast to the right function pointer type before calling it – newacct Jul 08 '13 at 08:55
  • @newacct 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. – Tricertops Jul 08 '13 at 09:26
  • 1
    @newacct is right, iMartin; if you're going to use `objc_msgSend()` you should always cast it: http://www.red-sweater.com/blog/320/abusing-objective-c-with-class and seel also http://code.google.com/p/rococoa/wiki/ObjcMsgSend – jscs Jul 08 '13 at 18:04
  • 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](http://www.sealiesoftware.com/blog/archive/2008/10/30/objc_explain_objc_msgSend_stret.html). – jscs Jul 08 '13 at 18:21
  • @JoshCaswell Thank you for links, interesting reading. Feel free to update the answer if you want. – Tricertops Jul 08 '13 at 20:09
  • @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). – smileyborg Jan 08 '14 at 20:26
  • @smileyborg Thanks! Few days ago I kicked myself in ass because I didn't cast `objc_msgSend` and it was crashing the app on ARMv8. – Tricertops Jan 09 '14 at 10:17
  • That answer is perfect!!!!!! I have added the one line of code that was missing, to stop Xcode whining. – Duck May 08 '14 at 15:03
  • @RubberDuck Thanks, I've added alternative (modern) way of importing. Also, I changed casting of function pointer to use `typeof()`. – Tricertops May 08 '14 at 19:42
  • @iMartin - can you explain that change? – Duck May 08 '14 at 20:03
  • 1
    @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. – Tricertops May 11 '14 at 18:37
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**).

justin
  • 104,054
  • 14
  • 179
  • 226
  • 1
    Compare rob mayoff's answer to the "possible duplicate": `__unsafe_unretained NSString *result` seems to be an elegant solution. – Martin R Jul 07 '13 at 07:23
  • @MartinR right, that solution is also good +1. thanks – justin Jul 07 '13 at 07:26
  • 1
    Thanks guys - apparently using obj_msgSend like in the answer above solved all my problems, that too with 1 line of code. – user2453876 Jul 07 '13 at 07:45