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.

ryanrhee
  • 2,550
  • 4
  • 23
  • 25

3 Answers3

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

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • 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. – ryanrhee Aug 08 '12 at 22:55
  • 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. – rob mayoff Aug 08 '12 at 22:57
  • 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. – rob mayoff Aug 08 '12 at 22:58
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
Aleksey
  • 875
  • 8
  • 8
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.

deleted_user
  • 3,817
  • 1
  • 18
  • 27
  • I'm using ARC, and variables are `__strong` by default, so the `retain` is happening, just not visibly. – ryanrhee Aug 08 '12 at 22:54
  • *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 stack – deleted_user Aug 08 '12 at 22:58
  • 1
    Thats why I dont use ARC. Glad you got your answer. – deleted_user Aug 08 '12 at 23:04
  • Yep, ARC seems to be inserting a `release` at the end of the scope. – ryanrhee Aug 08 '12 at 23:05
  • 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. – deleted_user Aug 08 '12 at 23:08
  • 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. – ryanrhee Aug 09 '12 at 18:36