NSInvocation是iOS开发中常见的用来实现反射的方法,即通过传入方法名和参数等格式化的字符串后,即可调用指定的方法,虽然牺牲了运行性能,但是对于模块解耦确实是个杀手锏,而NSInvocation充分体现了OC通过消息传递来调用方法的特性,是iOS开发中解耦的利器。
当我们需要获取NSInvocation调用方法的返回值时,会使用系统提供的- (void)getReturnValue:(id *retValue);
方法,调用的代码大概如下面所示:
NSMethodSignature *methodSignature = [AClass instanceMethodSignatureForSelector:NSSelectorFromString(action)];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[invocation setTarget:aInstance];
[invocation setSelector:NSSelectorFromString(action)];
NSObject *param1 = [[NSObject alloc] init];
[invocation setArgument:¶m1 atIndex:2];
id resultValue;
[invocation invoke];
[invocation getReturnValue:&resultValue];
return resultValue;
假如上述代码不幸在ARC模式下运行,那么恭喜你获得crash一枚,而且堆栈栈顶多半是objc_retain
或objc_release
等,有经验的你肯定知道这意味着我们内存管理出问题了,大概率出现了野指针。这时机智的你肯定会拿出Zombie Object工具,这工具确实很有用,很快我们就可以定位出过度释放是发生在action方法调用过程中,经过一轮查证,问题最有可能就是出现在NSInvocation调用过程中了,那么是参数还是返回值过度释放呢?
假如是参数,那么我们通过retainArguments
方法就理应解决了,可惜仍然发生了crash,情况并没有变化,因此crash真凶应该就锁定在action方法调用后的返回值了。经过参考苹果文档发现,getReturnValue
过程中,只是将原本的返回值按字节拷贝到参数所指的地址,因此这时并没有进行retain操作,更加不会有objc_autoreleaseReturnValue
和objc_retainAutoreleasedReturnValue
这些编译器优化,因此这时我们假如将上述代码转成汇编,就会发现ARC帮我们在整个方法最后添加了release方法,以为returnValue默认是__strong
修饰的,所以过度释放就发生在此处。
经过一轮辗转查证,我们找到crash的罪魁祸首,那我们该怎么避免呢?
既然ARC帮我们多加了一次不必要的release,那么有没有办法让ARC不加release呢,有的,那就是给resultValue显示指定__unsafe_unretained
修饰符,这个一直被我们认定为没用,不安全的修饰符,现在可以派上用场了,使用它,ARC自然就不会帮我们添加release,可是这样返回值在整个方法结束后就会被释放回收,所以,我们还需要再用一个__strong
修饰的变量持有住该返回值,具体代码如下:
id __unsafe_unretained tmpValue;
[invocation invoke];
[invocation getReturnValue:&tmpValue];
id resultValue = tmpValue;
return resultValue;
内存问题以难以定位著称,我们需要多利用instrument中的工具辅助,一步步排除干扰,提高我们的效率。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。