使用 NSInvocation 调用初始化器

Calling an initialiser using NSInvocation

我有一个场景,在 allocing 一个对象之后调用的初始化程序直到运行时才知道,我无法控制它。它也可能有各种论据。所以目前我正在这样做:

    ...
    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;
}

我遇到的问题(我认为!)是因为 initSelector 被 NSInvocation 调用,它没有返回 retain+1 对象。所以上面的结果是当自动释放池试图释放对象时崩溃。

我尝试添加 CFBridgingRetain(...) 来解决内存问题,但我不确定这是正确的解决方案,静态分析器将其标记为内存泄漏。

所以我的问题是如何通过 NSInvocation 调用初始化程序并取回正确的 retain+1 对象?

哇....我想我偶然发现了答案。我花了一些时间在网上搜索和阅读 Clang documentation on ARC。其中大部分充满了“如果”、“但是”和“也许”,我想我必须花很多时间才能理解它。无论如何,这是修改后的代码:

    ...
    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;
}

以某种方式向从初始化程序接收响应的变量添加 __unsafe_unretained 似乎解决了这个问题。我没有时间阅读 Clang 文档并弄清楚为什么会这样。我很高兴它确实如此。

也许对 ARC 的内存管理有一些高技术知识的人可以进一步解释。

只需将 invokeSelector 方法重命名为 createObjectByInvokingSelector,使其符合命名方案并且不会释放 returnValue

getReturnValue: 只是将 return 值复制到指针指向的位置,作为普通的旧哑二进制数据。它不关心类型是什么,它只是按二进制方式复制它,如果它是托管对象类型,它不会像内存管理那样对它做任何其他事情。

因此,给它传递一个id __strong *是不合适的,因为该类型要求当某物被分配给所指向的东西时,释放先前的值并保留新值(getReturnValue: 不会那样做。)

传递给它 id __unsafe_unretained * 是合适的,因为该类型完全符合将某些东西分配给指向的东西的行为不进行任何内存管理。这就是为什么将 returnValue 声明为 id __unsafe_unretained(或 MyClass * __unsafe_unretained)然后传递 &returnValue 有效。

在你解决这个问题之后,你所拥有的就可以调用普通方法了。但在这种情况下,您正在调用初始化器,而初始化器具有与普通方法不同的内存管理规则。初始化程序在调用它们的引用上消耗引用计数,并 return 保留引用。如果初始化器 return 是它被调用的对象(这是大多数初始化器所做的),那么它们就会取消并且它就像普通方法一样工作。但是,也允许初始化器

  1. Return 与调用它的对象不同的对象,在这种情况下,它会释放调用它的对象,并在 return 之前保留新对象,或者
  2. Return nil,在这种情况下它会释放它被调用的对象,并且return nil

因此它需要更复杂的处理才能与一般的初始化程序一起使用。我不会讨论那个。如果你知道你的初始化器总是 return 它被调用的对象,那么你不需要担心这个。