ryanrhee
ryanrhee

Reputation: 2571

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

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.

Upvotes: 7

Views: 3802

Answers (3)

Aleksey
Aleksey

Reputation: 1305

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

Upvotes: 4

rob mayoff
rob mayoff

Reputation: 385700

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

Upvotes: 25

deleted_user
deleted_user

Reputation: 3805

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.

Upvotes: 0

Related Questions