【问题标题】:Calling an initialiser using NSInvocation使用 NSInvocation 调用初始化程序
【发布时间】:2015-08-06 08:09:44
【问题描述】:

我有一个场景,在 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 对象?

【问题讨论】:

  • 为什么创建一个局部变量obj而不使用呢?
  • // ... setting arguments ... 会发生什么?
  • 另外,如果你同步调用它,你不应该使用retainArguments
  • 对不起,代码是从原始代码中删减的。因此,拼写错误没有将 obj 传递给该方法。原件确实重用了 NSInvocation,因此保留了。在参数部分,代码做了一些工作来确定和设置初始化器的参数。

标签: objective-c memory nsinvocation


【解决方案1】:

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

因此,将id __strong * 传递给它是不合适的,因为该类型要求在将某些内容分配给指向的事物时,释放先前的值并保留新值(getReturnValue: 不这样做.)

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

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

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

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

【讨论】:

  • 感谢您的详细解释。它确实帮助我理解了正在发生的事情。
【解决方案2】:

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

【讨论】:

  • 我查看了有关命名的内容,但代码也用于调用其他方法。不过感谢您的建议。
【解决方案3】:

哇......我想我偶然发现了答案。我花了一些时间搜索网络并阅读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 的内存管理有一些高度技术知识的人可以进一步解释一下。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-06-16
    • 2014-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-18
    • 2015-11-10
    相关资源
    最近更新 更多