objective-cmemorynsinvocation

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?


Solution

  • 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

    1. 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
    2. 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.