NSInvocation和内存问题

8
我来自Java世界,我们对于内存管理问题一直都是无知的。大多数情况下,ARC已经为我解决了很多问题,但是有一件事情让我感到困惑。基本上我正在使用NSInvocations处理一些东西,在进行以下代码修改之前,我遇到了一些令人头疼的内存问题。自从我进行了这些修改后,内存崩溃问题就消失了,但是我通常对自己不理解的代码非常害怕。请问我的做法正确吗?
以前的代码:各种内存问题:
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[target class] instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:target];
[invocation setArgument:&data atIndex:2];
[invocation setArgument:&arg atIndex:3];
[invocation invoke];

NSString *returnValue;
[invocation getReturnValue:&returnValue];

之后:没有内存问题,但我不确定我理解得对。
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[target class] instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:target];
[invocation setArgument:&data atIndex:2];
[invocation setArgument:&arg atIndex:3];
[invocation invoke];

CFTypeRef result;
[invocation getReturnValue:&result];

if (result)
    CFRetain(result);

NSString *returnValue = (__bridge_transfer NSString *)result;

编辑:

基于下面回答的原因,我使用了objc_msgSend, 如下所示:

NSString * returnValue = objc_msgSend(target, selector, data, arg);

它解决了所有内存问题,并且看起来更简单。如果您发现任何问题,请进行评论。


2
这不太可能是正确的修复方式,但选择器是什么,具体而言,内存问题是什么? - jscs
选择器是目标类上的一个简单方法。它接受两个对象并返回一个字符串。问题各不相同 - 大多数是访问错误。 - user2453876
2个回答

5
我会这样回答你的问题:不要使用NSInvocation 这只是一个友好的建议,如果可能的话,请避免使用它。
在Objective-C中有很多很好的方法来进行回调,以下是其中两种对您可能有用的:
  • Blocks: Defined in context, choose any argument count and types, possible issues with memory too. There are many resources on how to use them.
  • performSelector: max 2 object arguments, invoked using:

    [target performSelector:selector withObject:data withObject:args];
    

此外,当我需要使用四个参数调用选择器时,我仍然不使用NSInvocation,而是直接调用objc_msgSend

id returnValue = objc_msgSend(target, selector, data, /* argument1, argument2, ... */);

简单。

编辑:使用objc_msgSend方法时需要注意返回值。如果您的方法返回一个对象,请使用上述方法。如果它返回一个原始类型,则需要转换objc_msgSend方法,以便编译器知道发生了什么(请参见此链接)。这是一个带有一个参数并返回BOOL的方法示例:

// Cast the objc_msgSend function to a function named BOOLMsgSend which takes one argument and has a return type of BOOL.
BOOL (*BOOLMsgSend)(id, SEL, id) = (typeof(BOOLMsgSend)) objc_msgSend;
BOOL ret = BOOLMsgSend(target, selector, arg1);

如果您的方法返回一个结构体,则情况会变得有点复杂。您可能(但不总是)需要使用objc_msgSend_stret——在这里查看更多信息编辑:如果不加入此行代码,Xcode会抱怨:
#import <objc/message.h>

或者

@import ObjectiveC.message;

1
我认为块是更好的选择。我不建议使用objc_msgSend(),因为它是一个运行时函数,你必须了解各种返回类型。关于objc_msgSend()performSelector的注意事项是ARC编译器不知道返回对象的所有权语义(它是否是+1保留的对象)。 - Martin R
@iMartin:应该使用alloc(而不是init!)、newcopymutableCopy。是的,NSInvocation也有同样的问题。我的观点主要是块是更好的解决方案。 - Martin R
1
在调用 objc_msgSend(或 objc_msgSend_stret)之前,需要将其转换为正确的函数指针类型。 - newacct
1
@newacct是正确的,iMartin;如果您要使用objc_msgSend(),则应始终对其进行强制转换:http://www.red-sweater.com/blog/320/abusing-objective-c-with-class以及请参见http://code.google.com/p/rococoa/wiki/ObjcMsgSend - jscs
1
@RubberDuck typeof(x) 是编译器的一个特性,它会被替换为 x 的实际类型。这样你就不必在同一行上重复两次 BOOL (*)(id, SEL, id) 的类型了。 - Tricertops
显示剩余18条评论

4

如果可能的话,您应该通常考虑使用块作为更好的替代方案(它们成功地取代了NSInvocation)。

至于返回值,您可以使用以下代码:

CFTypeRef result = NULL;
[invocation getReturnValue:&result];    
NSString *returnValue = (__bridge NSString *)result;

这里的基本问题在于,-getReturnValue: 方法从ARC的角度来看没有返回一个输出对象。因此,它可能会处理引用计数操作不当(编译器会在ARC中为您添加这些操作),因为 -getReturnValue: 的参数是 void* 而不是输出对象(例如 NSObject**)。

1
将Rob Mayoff的答案与“可能重复”的答案进行比较:__unsafe_unretained NSString *result似乎是一种优雅的解决方案。 - Martin R
@MartinR没错,那个解决方案也很好 +1。谢谢 - justin
1
谢谢大家 - 显然像上面答案中使用obj_msgSend解决了我所有的问题,而且只需要一行代码。 - user2453876

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接