NSInvocation getReturnValue: called inside forwardInvocation: makes the returned object call dealloc:
Asked Answered
R

3

7

Here's a standalone test.m file that I'm using to test the behavior.

To compile: clang test.m -o test.app -fobjc-arc -ObjC -framework Foundation. Make sure the Xcode command-line tools are installed.

#import <Foundation/Foundation.h>

@protocol Protocol

@optional
- (id)objProxyMethod;

@end

@interface ReturnObject: NSObject

@end

@interface Test : NSObject <Protocol>

@end

@interface Proxy : NSObject <Protocol>

- (id)objProxyMethod;

@end

@implementation ReturnObject

- (void)dealloc {
    NSLog(@"ERROR:");
    NSLog(@"I'm getting deallocated!");
    NSLog(@"This shouldn't happen!");
}

- (NSString *)description {
    return @"Blank object!";
}

@end

@implementation Proxy

- (id)objProxyMethod {
    NSLog(@"in [Proxy objProxyMethod]!");
    return [[ReturnObject alloc] init];
}

@end

@implementation Test

- (void)forwardInvocation:(NSInvocation *)invocation {
    NSLog(@"Forwarded invocation!");
    Proxy *proxy = [[Proxy alloc] init];
    [invocation invokeWithTarget: proxy];
    NSUInteger length = [[invocation methodSignature] methodReturnLength];
    if (length == 8) {
        id result;
        [invocation getReturnValue:&result];
    }
}

@end

int main () {
    Test *test = [[Test alloc] init];
    id objResult = [test objProxyMethod];
    NSLog(@"objResult = \"%@\"", objResult);

    return 0;
}

If I comment out [invocation getReturnValue:&result];, the returned object isn't deallocated. I don't know if this is a bug, or just me misunderstanding how NSInvocation works.

Reconciliatory answered 8/8, 2012 at 22:20 Comment(0)
D
25

The problem is that result is __strong by default, so when it goes out of scope, the compiler generates a release for it. But getReturnValue: didn't give you ownership of the returned object, so your method shouldn't be releasing it.

You can fix this by changing the declaration of result:

__unsafe_unretained id result;

This prevents the compiler from generating a release for result when result goes out of scope. If you need to retain it, you can copy it to another, __strong variable.

You could also add a category to NSInvocation to handle this for you:

@interface NSInvocation (ObjectReturnValue)

- (id)objectReturnValue;

@end

@implementation NSInvocation (ObjectReturnValue)

- (id)objectReturnValue {
    __unsafe_unretained id result;
    [self getReturnValue:&result];
    return result;
}

@end

...
    if (length == 8) {
        id result = [invocation objectReturnValue];
    }
...

You could also report this as a bug. I would expect the compiler, or at least the static analyzer, to warn you that you're converting a pointer to a strong id to a void pointer. http://bugreport.apple.com

Dreibund answered 8/8, 2012 at 22:40 Comment(3)
Thanks! Will it still work if I use __weak instead of __unsafe_unretained? I'm not getting any errors when I use __weak so far, but I just want to make sure.Reconciliatory
You should not use __weak instead of __unsafe_unretained here. A weak variable needs to be set using the objc_storeWeak runtime function, and -[NSInvocation getReturnValue:] clearly doesn't do that.Dreibund
If you declare __weak id result, the compiler will generate a call to objc_destroyWeak when result goes out of scope. Since result wasn't set using objc_storeWeak, the behavior is undefined.Dreibund
G
4

It was because ARC cannot manage objects which was written as pointers. Only directly assignment.

Wrong:

id result;
[invocation getReturnValue:&result];

Right:

void *pointer;
[invocation getReturnValue:&pointer];

id result = (__bridge id)pointer; //Correct, ARC will retain pointer after assignment
Globetrotter answered 29/1, 2014 at 8:55 Comment(0)
R
0
if (length == 8) {
    id result; //this is nil (its also a stack allocated pointer)
    [invocation getReturnValue:&result];  //sets the value to an object
}

...method ends object is deallocated

You must set the result to to a pointer which is not stack allocated or not call getReturnValue.

The API may assume that since you called getReturnValue that you are going to retain (and possibly consume the return value). You didnt. When you remove getReturnValue does the return value come back properly in the main method? The apple docs say that the return value is returned automatically.

Im assuming it does.

Russom answered 8/8, 2012 at 22:36 Comment(6)
I'm using ARC, and variables are __strong by default, so the retain is happening, just not visibly.Reconciliatory
id is a stack allocated variable. its not on the heap, it ceases to exist at the end of that method call ARC may be inserting a release there because the value is not explicitly returned on the call stackRussom
Thats why I dont use ARC. Glad you got your answer.Russom
Yep, ARC seems to be inserting a release at the end of the scope.Reconciliatory
if the value was set to something not on the stack ARC would probably let it live but the answer about using the __magic is good to know.Russom
I think you're right. Had I done a malloc() with the length, and used that spot on the heap as the pointer instead, I would have been OK. Then I would have just free()d the pointer, and the object pointed to wouldn't have been released. But I also agree, the __magic is good to know.Reconciliatory

© 2022 - 2024 — McMap. All rights reserved.