Why NSInvocation return value creates a zombie? - objective-c

I am trying to build a JavaScript to Native communication. For that purpose I need to execute dynamically a method on some class when JavaScript calls it.
I have a problem with NSInvocation getting the return value. When the getReturnValue is used the app crashes due to zombie. The zombie is indicated to be coming from the invocation called method's return value.
If I comment out the [invocation getReturnValue:&result]; line the app doesn't break.
The test method I am currently calling returns and (NSString *)
If I make the invoked selector method implementation return a literal string like #"firstsecond") the app doesn't break as well.
Why does it need a reference to it any way when the invocation method has already been executed and a string is returned. Isn't the returned string copied to the id result.
- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message {
if ([#"Native_iOS_Handler" isEqualToString: message.name]) {
NSArray *arguments = [message.body valueForKey:#"arguments"];
NSNumber *callbackID = [message.body valueForKey:#"callbackID"];
NSString *APIName = [message.body valueForKey:#"APIName"];
NSString *methodName = [message.body valueForKey:#"methodName"];
id classAPI = [self.exposedAPIs objectForKey:APIName];
SEL methodToRun = [classAPI getSelectorForJSMethod:methodName];
NSMethodSignature *methodSignature = [classAPI methodSignatureForSelector:methodToRun];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setTarget:classAPI];
[invocation setSelector:methodToRun];
id result;
[invocation invoke];
[invocation getReturnValue:&result];
NSLog(#"%#", result);// output: firstsecond
}
}
//the selector in this case is this
-(NSString*)getFoo{
// Why is this a zombie????
return [NSString stringWithFormat:#"%#%#", #"first", #"second"];
// This works:
//return #"fristsecond"
}
Although the selector in Instruments is different the result is the same. From this picture I understand what I have told you. I have no experience whit Instruments.

You fell victim of ARC not being aware of the way NSInvocation works modifying the result indirectly through another pointer. It's a known problem described here.
What happens is the resulting object indirectly becomes equal to result but ARC is not aware of it and will never retain it.
Without going into too much details NSString is a class cluster. What it effectively means is the implementation underneath changes based on how the string is created and used. Details of it are hidden while interacting with it in obj-c and Apple put a lot of effort to make it seamless to iOS developers. Your case is somewhat special.
Typically you will be getting:
__NSCFConstantString (e.g. #"constant") - string constant for app lifetime , for your case it happens to work, but you should never rely on that
NSTaggedPointerString (e.g. [[#"a"] mutableCopy] copy]) - an optimised short string with internal lookup table.
__NSCFString (e.g. [#"longString" mutableCopy] copy]) long string CoreFoundation representation.
At any time NSString may change implementation underneath so you should never make assumptions about it. Case 3 will immediately go out of scope after returning and get deallocated in next run loop, case 1 will never get deallocated, case 2 (?), but for sure will survive the next run loop.
So basially ARC isn't clever enough to associate the potentially deallocated object with the id result and you run into your problems.
How to fix it?
Use one of these:
1.
void *tempResult;
[invocation getReturnValue:&tempResult];
id result = (__bridge id) tempResult;
2.
__unsafe_unretained id tempResult;
[invocation getReturnValue:&tempResult];
result = tempResult;
Edit
As noted by #newacct in his comment getReturnValue: doesn't do registration of weak pointers hence it's inappropriate in this case. The pointer would not get zeroed when the object gets deallocated.
3.
__weak id tempResult;
[invocation getReturnValue:&tempResult];
result = tempResult;

Related

Calling an initialiser using NSInvocation

I have a scenario where the initialiser to use called after allocing an object is not known until runtime and I have no control over it. It also may have various arguments. So currently I'm doing this:
...
id obj = [MyClass alloc];
return [self invokeSelectorOn:obj];
}
-(id) invokeSelectorOn:(id) obj {
SEL initSelector = ...;
NSMethodSignature *sig = [[MyClass class] instanceMethodSignatureForSelector:initSelector];
NSinvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
inv.selector = initSelector;
[inv retainArguments];
// ... setting arguments ...
[inv invokeWithTarget:obj];
id returnValue;
[inv getReturnValue:&returnValue];
return returnValue;
}
The problem (I think !) I have is that because initSelector is being called by an NSInvocation, it's not returning a retain+1 object. So the result of the above is a crash when the auto release pool attempts to dealloc the object.
I've tried adding a CFBridgingRetain(...) which fixes the memory issue, however I'm not sure this is the correct solution and the static analyser tags it as a memory leak.
So my question is how can I can I call an initialiser via a NSInvocation and get back a correctly retain+1 object?
getReturnValue: simply copies the return value into the location pointed to by your pointer as plain old dumb binary data. It doesn't care what the type is, it just copies it binarywise, and does nothing else with it like memory management if it's a managed object type.
Therefore, passing it a id __strong * is not appropriate, since that type requires that when something is assigned to the thing pointed to, the previous value is released and the new value is retained (getReturnValue: doesn't do that.)
Passing it a id __unsafe_unretained * is appropriate, since that type exactly matches the behavior where assigning something to the thing pointed to doesn't do any memory management. That's why declaring returnValue to be id __unsafe_unretained (or MyClass * __unsafe_unretained) and then passing &returnValue works.
After you fix this, what you have is fine for calling normal methods. But in this case you are calling initialisers, and initialisers have different memory management rules than normal methods. Initializers consume a reference count on the reference they are called on, and return a retained reference. If the initializer returns the object it was called on (which is what most initializers do), then those cancel out and it works just like normal methods. However, initializers are also allowed to
Return a different object than the one it was called on, in which case it would release the one it was called on, and retain the new one before returning it, or
Return nil, in which case it would release the object it was called on, and return nil.
So more complex handling is needed for it to work with initializers in general. I will not go into that. If you know your initialisers always return the object it was called on, then you don't need to worry about this.
Just rename the invokeSelector method to createObjectByInvokingSelector so it fits with the naming scheme and are doesn't free the returnValue
Whoa .... I think I've stumbled on the answer. I've spent some time searching the net and reading the Clang documentation on ARC. Most of which is so full of 'if's, 'but's and 'maybe's, I think I'd have to spend quite some time to understand it. Anyway, here is the modified code:
...
id obj = [MyClass alloc];
return [self invokeSelectorOn:obj];
}
-(id) invokeSelectorOn:(id) obj {
SEL initSelector = ...;
NSMethodSignature *sig = [[MyClass class] instanceMethodSignatureForSelector:initSelector];
NSinvocation *inv = [NSInvocation invocationWithMethodSignature:sig];
inv.selector = initSelector;
[inv retainArguments];
// ... setting arguments ...
[inv invokeWithTarget:obj];
id __unsafe_unretained returnValue;
[inv getReturnValue:&returnValue];
return returnValue;
}
Somehow the addition of __unsafe_unretained to the variable that receives the response from the initialiser seems to fix the problem. I have not had the time to work through the Clang doc and figure out why this works. I'm just happy it does.
Perhaps someone with some highly technical knowledge of the memory management of ARC could explain further.

NSDictionary of Blocks as argument cause overflow

I understand blocks are objective c objects and can be put in NSDictionary directly without Block_copy when using ARC. But I got EXC_BAD_ACCESS error with this code:
- (void)viewDidLoad
{
[super viewDidLoad];
[self method1:^(BOOL result){
NSLog(#"method1WithBlock finished %d", result);
}];
}
- (void) method1:(void (^)(BOOL))finish{
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:^(NSData *rcb){
finish(YES);
}, #"success",
^(NSError *error){
finish(NO);
}, #"failure", nil];
[self method2:dict];
}
- (void) method2:(NSDictionary *)dict{
void (^success)(NSData *rcb) = [dict objectForKey:#"success"];
success(nil);
}
If I change method1: to this, no error raised.
- (void) method1:(void (^)(BOOL))finish{
void (^success)(NSData *) = ^(NSData *rcb){
finish(YES);
};
void (^failure)(NSError *error) = ^(NSError *error){
finish(NO);
};
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:success, #"success",
failure, #"failure", nil];
[self method2:dict];
}
Can anybody explain why I have to use automatic variables to store the blocks before putting them to dictionary ?
I am using iOS SDK 6.1.
According to the "Transitioning to ARC Release Notes", you have to copy a block stored
in a dictionary (emphasis mine):
Blocks “just work” when you pass blocks up the stack in ARC mode, such
as in a return. You don’t have to call Block Copy any more.
You still need to use [^{} copy] when passing “down” the stack into
arrayWithObjects: and other methods that do a retain.
The second method works "by chance" because success and failure are a
__NSGlobalBlock__ and not a "stack based block" that needs to be copied to the heap.
I understand blocks are objective c objects and can be put in NSDictionary directly without Block_copy when using ARC.
No, they're not common objects. When you create a block it is on the stack, and it doesn't matter of what is it's retain count, when you exit form the function it will be popped from the stack. Copy it to make it stay alive.
You must copy blocks before passing them to a method when 1) the block will be stored for longer than the duration of the call, and 2) the parameter you are passing it to is a normal object pointer type (i.e. id or NSObject *) instead of a block type.
This is the case for your call. dictionaryWithObjectsAndKeys: stores the argument in the resulting dictionary, and it simply expects normal object pointer arguments (of type id), and does not know whether you are passing blocks or not.
The reason for the second condition I said is because if the method parameter already takes a block type (e.g. for any completion handler parameters), then that method is already aware of the special memory management requirements of blocks, and therefore will take responsibility for copying the block if it needs to store it. In that case the caller doesn't need to worry about it. However, in your case, you are passing it to a general method that doesn't know it's getting a block, and thus doesn't know to copy it. So the caller must do it.
Can anybody explain why I have to use automatic variables to store the
blocks before putting them to dictionary ?
About this, your second example happens to work because recent versions of the ARC compiler is super conservative about blocks and inserts copies whenever you assign it to a block type variable. However, this is not guaranteed by the ARC specification, and is not guaranteed to work in the future, or in another compiler. You should not rely on this behavior.

Does -[NSInvocation retainArguments] copy blocks?

NSInvocation's -retainArguments method is useful for when you don't run the NSInvocation immediately, but do it later; it retains the object arguments so they remain valid during this time.
As we all know, block arguments should be copied instead of retained. My question is, does -retainArguments know to copy instead of retain an argument when it's of block type? The documentation does not indicate that it does, but it seems like an easy and sensible thing to do.
Update: The behavior seems to have changed in iOS 7. I just tested this, and in iOS 6.1 and before, -retainArguments didn't copy parameters of block type. In iOS 7 and later, -retainArguments does copy parameters of block type. The documentation of -retainArguments has been updated to say that it copies blocks, but it does not say when the behavior changed (which is really dangerous for people who support older OS's).
It's certainly supposed to (albeit I haven't test it myself). According to the documentation:
retainArguments
If the receiver hasn’t already done so, retains the
target and all object arguments of the receiver and copies all of its
C-string arguments and blocks.
(void)retainArguments
Discussion
Before this method is invoked, argumentsRetained returns NO; after, it returns YES.
For efficiency, newly created NSInvocation objects don’t retain or
copy their arguments, nor do they retain their targets, copy C
strings, or copy any associated blocks. You should instruct an
NSInvocation object to retain its arguments if you intend to cache it,
because the arguments may otherwise be released before the invocation
is invoked. NSTimer objects always instruct their invocations to
retain their arguments, for example, because there’s usually a delay
before a timer fires.
No.
Image if the answer is yes, where NSInvocation is smart enough to copy block, it should do something like this:
for (/*every arguments*/) {
if (/*arg is object. i.e. #encode(arg) is '#'*/) {
if ([arg isKindOfClss:[NSBlock class]]) {
arg = [arg copy]; // copy block
} else {
[arg retain];
}
}
}
the problem is that arg is modified while copying the block, which should not happen because this means call retainArguments may change the arguments in the NSInvocation. this will break many assumptions that already made. (i.e. arguments get from NSInvocation should be same as arguments used to create the NSInvocation)
Update
just did the test to conform the answer is NO, but my previous point was incorrect though...
#interface Test : NSObject
#end
#implementation Test
- (void)testMethodWithBlock:(void (^)(void))block obj:(id)obj cstr:(const char *)cstr {
NSLog(#"%p %p %p %#", block, obj, cstr, [block class]);
}
#end
#implementation testTests
- (void)test1 {
__block int dummy;
Test *t = [[Test alloc] init];
NSMethodSignature *ms = [t methodSignatureForSelector:#selector(testMethodWithBlock:obj:cstr:)];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:ms];
void (^block)(void) = ^ {
dummy++; // stop this become global block
};
id obj = #"object";
char *cstr = malloc(5);
strcpy(cstr, "cstr");
NSLog(#"%#", [ms debugDescription]);
NSLog(#"%p %p %p %#", block, obj, cstr, [block class]);
[invocation setSelector:#selector(testMethodWithBlock:obj:cstr:)];
[invocation setArgument:&block atIndex:2];
[invocation setArgument:&obj atIndex:3];
[invocation setArgument:&cstr atIndex:4];
[invocation invokeWithTarget:t];
[invocation retainArguments];
[invocation invokeWithTarget:t];
free(cstr);
}
#end
output, ARC disabled (and crashed):
2013-04-18 19:49:27.616 test[94555:c07] 0xbfffe120 0x70d2254 0x7167980 __NSStackBlock__
2013-04-18 19:49:27.617 test[94555:c07] 0xbfffe120 0x70d2254 0x7167980 __NSStackBlock__
2013-04-18 19:49:27.618 test[94555:c07] 0xbfffe120 0x70d2254 0x736a810 __NSStackBlock__
ARC enabled:
2013-04-18 19:51:03.979 test[95323:c07] 0x7101e10 0x70d2268 0x7101aa0 __NSMallocBlock__
2013-04-18 19:51:03.979 test[95323:c07] 0x7101e10 0x70d2268 0x7101aa0 __NSMallocBlock__
2013-04-18 19:51:03.980 test[95323:c07] 0x7101e10 0x70d2268 0xe0c1310 __NSMallocBlock__
as you can see, c string are copied by retainArguments but not blocks. but with ARC enabled, the problem should go away because ARC copied it for you at some point.

getArgument of NSInvocation of current method always returns null

I want to get the name of the arguments of the current function I am in so that I can prepare loading that object from the filesystem if it's not present on the current instance. (for instance if [foo dictTest] is not available I want to load it's prior saved plist version into exactly that ivar)
I want to find the file by providing the ivar name that I provided as an argument to the current function.
This is the function code:
-(NSDictionary*)getCachedDictionary:(NSDictionary*)dict{
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:_cmd]];
NSString * firstArgument = nil;
[invocation getArgument:&firstArgument atIndex:2];
NSLog(#"Trying to get the objects ivar %#",firstArgument);
// right now during testing return nil
return nil;
}
As soon as the code reaches the NSLog I am getting a null value from firstArgument.
Why is that? Could it be possible that I would have to wait for the complete invocation of that current method I am in or is it actually better to create a proxy function that implicitly calls my class method via an invocation that eats the ivar name provided by setArgument so that I can use that argument string like I want?
Thanks a lot in advance!
P.S.: In this particular example I do not want to use KVC to identify the ivar and return it.
You've misunderstood the NSInvocation API. +[NSInvocation invocationWithMethodSignature:] creates a new NSInvocation that is keyed to accept arguments of the types defined by the method signature. It does not return an NSInvocation that corresponds to the current method invocation. This is pretty easy to see why:
- (void)doBar:(id)bip {
NSLog(#"hi there!")
}
- (void)doFoo {
NSMethodSignature *sig = [self methodSignatureForSelector:#selector(doBar:)];
NSInvocation *i = [NSInvocation invocationWithMethodSignature:sig];
}
When you create the invocation in doFoo for the doBar: method, it's obvious to see that the arguments must be empty, because doBar: hasn't been executed, and thus there is no argument. Changing #selector(doBar:) to _cmd wouldn't magically change anything.
So the next question: is there a way to get an NSInvocation for the current method invocation? Not that I know of. NSInvocation is an extremely complicated class, and constructing one from the current method would be a nightmare.
I strongly suggest finding a different approach to do whatever it is you want to do.
Even though the question is old and answered, here is a link that provides an easy and very elegant way to create an invocation instance for any selector/method that is known at compile time:
http://www.cocoawithlove.com/2008/03/construct-nsinvocation-for-any-message.html

Why does the ARC migrator say that NSInvocation's -setArgument: is not safe unless the argument is __unsafe_unretained?

I was migrating a block of code to automatic reference counting (ARC), and had the ARC migrator throw the error
NSInvocation's setArgument is not safe to be used with an object with
ownership other than __unsafe_unretained
on code where I had allocated an object using something like
NSDecimalNumber *testNumber1 = [[NSDecimalNumber alloc] initWithString:#"1.0"];
then set it as an NSInvocation argument using
[theInvocation setArgument:&testNumber1 atIndex:2];
Why is it preventing you from doing this? It seems just as bad to use __unsafe_unretained objects as arguments. For example, the following code causes a crash under ARC:
NSDecimalNumber *testNumber1 = [[NSDecimalNumber alloc] initWithString:#"1.0"];
NSMutableArray *testArray = [[NSMutableArray alloc] init];
__unsafe_unretained NSDecimalNumber *tempNumber = testNumber1;
NSLog(#"Array count before invocation: %ld", [testArray count]);
// [testArray addObject:testNumber1];
SEL theSelector = #selector(addObject:);
NSMethodSignature *sig = [testArray methodSignatureForSelector:theSelector];
NSInvocation *theInvocation = [NSInvocation invocationWithMethodSignature:sig];
[theInvocation setTarget:testArray];
[theInvocation setSelector:theSelector];
[theInvocation setArgument:&tempNumber atIndex:2];
// [theInvocation retainArguments];
// Let's say we don't use this invocation until after the original pointer is gone
testNumber1 = nil;
[theInvocation invoke];
theInvocation = nil;
NSLog(#"Array count after invocation: %ld", [testArray count]);
testArray = nil;
due to the overrelease of testNumber1, because the temporary __unsafe_unretained tempNumber variable is not holding on to it after the original pointer is set to nil (simulating a case where the invocation is used after the original reference to an argument has gone away). If the -retainArguments line is uncommented (causing the NSInvocation to hold on to the argument), this code does not crash.
The exact same crash happens if I use testNumber1 directly as an argument to -setArgument:, and it's also fixed if you use -retainArguments. Why, then, does the ARC migrator say that using a strongly held pointer as an argument to NSInvocation's -setArgument: is unsafe, unless you use something that is __unsafe_unretained?
This is a complete guess, but might it be something to do with the argument being passed in by reference as a void*?
In the case you've mentioned, this doesn't really seem a problem, but if you were to call, eg. getArgument:atIndex: then the compiler wouldn't have any way of knowing whether the returned argument needed to be retained.
From NSInvocation.h:
- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
Given that the compiler doesn't know whether the method will return by reference or not (these two method declarations have identical types and attributes), perhaps the migrator is being (sensibly) cautious and telling you to avoid void pointers to strong pointers?
Eg:
NSDecimalNumber* val;
[anInvocation getArgument:&val atIndex:2];
anInvocation = nil;
NSLog(#"%#", val); // kaboom!
__unsafe_unretained NSDecimalNumber* tempVal;
[anInvocation getArgument:&tempVal atIndex:2];
NSDecimalNumber* val = tempVal;
anInvocation = nil;
NSLog(#"%#", val); // fine
An NSInvocation by default does not retain or copy given arguments for efficiency, so each object passed as argument must still live when the invocation is invoked. That means the pointers passed to -setArgument:atIndex: are handled as __unsafe_unretained.
The two lines of MRR code you posted got away with this: testNumber1 was never released. That would have lead to a memory leak, but would have worked. In ARC though, testNumber1 will be released anywhere between its last use and the end of the block in which it is defined, so it will be deallocated. By migrating to ARC, the code may crash, so the ARC migration tool prevents you from migrating:
NSInvocation's setArgument is not safe to be used with an object with
ownership other than __unsafe_unretained
Simply passing the pointer as __unsafe_unretained won't fix the problem, you have to make sure that the argument is still around when the invocation gets called. One way to do this is call -retainArguments as you did (or even better: directly after creating the NSInvocation). Then the invocation retains all its arguments, and so it keeps everything needed for being invoked around. That may be not as efficient, but it's definitely preferable to a crash ;)
Why is it preventing you from doing this? It seems just as bad to use __unsafe_unretained objects as arguments.
The error message could be improved but the migrator is not saying that __unsafe_unretained objects are safe to be used with NSInvocation (there's nothing safe with __unsafe_unretained, it is in the name). The purpose of the error is to get your attention that passing strong/weak objects to that API is not safe, your code can blow up at runtime, and you should check the code to make sure it won't.
By using __unsafe_unretained you are basically introducing explicit unsafe points in your code where you are taking control and responsibility of what happens. It is good hygiene to make these unsafe points visible in the code when dealing with NSInvocation, instead of being under the illusion that ARC will correctly handle things with that API.
Throwing in my complete guess here.
This is likely directly related to retainArguments existing at all on the invocation. In general all methods describe how they will handle any arguments sent to them with annotations directly in the parameter. That can't work in the NSInvocation case because the runtime doesn't know what the invocation will do with the parameter. ARC's purpose is to do its best to guarantee no leaks, without these annotations it is on the programmer to verify there isn't a leak. By forcing you to use __unsafe_unretained its forcing you to do this.
I would chalk this up to one of the quirks with ARC (others include some things not supporting weak references).
The important thing here is the standard behaviour of NSInvocation:
By default, arguments are not retained and C string arguments are not being copied. Therefore under ARC your code can behave as follows:
// Creating the testNumber
NSDecimalNumber *testNumber1 = [[NSDecimalNumber alloc] initWithString:#"1.0"];
// Set the number as argument
[theInvocation setArgument:&testNumber1 atIndex:2];
// At this point ARC can/will deallocate testNumber1,
// since NSInvocation does not retain the argument
// and we don't reference testNumber1 anymore
// Calling the retainArguments method happens too late.
[theInvocation retainArguments];
// This will most likely result in a bad access since the invocation references an invalid pointer or nil
[theInvocation invoke];
Therefore the migrator tells you:
At this point you have to explicitly ensure that your object is being retained long enough. Therefore create an unsafe_unretained variable (where you have to keep in mind that ARC won't manage it for you).
According to Apple Doc NSInvocation:
This class does not retain the arguments for the contained invocation by default. If those objects might disappear between the time you create your instance of NSInvocation and the time you use it, you should explicitly retain the objects yourself or invoke the retainArguments method to have the invocation object retain them itself.