BigBang icon indicating copy to clipboard operation
BigBang copied to clipboard

double release导致崩溃 和 内存泄露

Open zljkevin opened this issue 5 years ago • 0 comments

double release: 从 -forwardInvocation: 里的 NSInvocation 对象取参数值时,若参数值是id类型,我们会这样取:

id arg; [invocation getArgument:&arg atIndex:i]; 但这样的写法会导致 crash,这是因为 id arg 在ARC下相当于 __strong id arg,若这时在代码显式为 arg 赋值,根据 ARC 的机制,会自动插入一条 retain 语句,然后在退出作用域时插入 release 语句:

  • (void)method { id arg = [SomeClass getSomething]; // [arg retain] ... // [arg release] 退出作用域前release } 但我们这里不是显式对 arg 进行赋值,而是传入 -getArgument:atIndex: 方法,在这里面赋值后 ARC 没有自动给这个变量插入 retain 语句,但退出作用域时还是自动插入了 release 语句,导致这个变量多释放了一次,导致 crash。解决方法是把 arg 变量设成 __unsafe_unretained 或 __weak,让 ARC 不在它退出作用域时插入 release 语句即可:

__unsafe_unretained id arg; [invocation getReturnValue:&arg]; 还可以通过 __bridge 转换让局部变量持有返回对象,这样做也是没问题的:

id returnValue; void *result; [invocation getReturnValue:&result]; returnValue = (__bridge id)result;

内存泄露: 当 NSInvocation 调用的是 alloc 时,返回的对象并不会释放,造成内存泄露,只有把返回对象的内存管理权移交出来,让外部对象帮它释放才行:

id returnValue; void *result; [invocation getReturnValue:&result]; if ([selectorName isEqualToString:@"alloc"] || [selectorName isEqualToString:@"new"] || [selectorName isEqualToString:@"copy"] || [selectorName isEqualToString:@"mutableCopy"]) { returnValue = (__bridge_transfer id)result; } else { returnValue = (__bridge id)result; } 这是因为 ARC 对方法名有约定,当方法名开头是 alloc / new / copy / mutableCopy 时,返回的对象是 retainCount = 1 的,除此之外,方法返回的对象都是 autorelease 的,按上一节的说法,对于普通方法返回值,ARC 会在赋给 strong 变量时自动插入 retain 语句,但对于 alloc 等这些方法,不会再自动插入 retain 语句:

id obj = [SomeObject alloc]; //alloc 方法返回的对象 retainCount 已 +1,这里不需要retain

id obj2 = [SomeObj someMethod]; //方法返回的对象是 autorelease,ARC 会再这里自动插入 [obj2 retain] 语句 而 ARC 并没有处理非显式调用时的情况,这里动态调用这些方法时,ARC 都不会自动插入 retain,这种情况下,alloc / new 等这类方法返回值的 retainCount 是会比其他方法返回值多1的,所以需要特殊处理这类方法。

zljkevin avatar Jul 31 '19 03:07 zljkevin