在我使用的第三方库中,我得到警告
"块捕获自动释放的out-parameter">
是什么问题,我怎么解决它?
- (BOOL)register:(NSString *)param error:(NSError **)errPtr
{
__block BOOL result = YES;
__block NSError *err = nil;
dispatch_block_t block = ^{ @autoreleasepool {
NSMutableArray *elements = [NSMutableArray array];
/**** Block captures an autoreleasing out-parameter,
which may result in use-after-free bugs ****/
/* on errPtr */
[self registerWithElements:elements error:errPtr];
}};
if (errPtr)
*errPtr = err;
return result;
}
当你有一个间接的非常量参数(T **param
) Clang与ARC自动限定这样的参数与__autoreleasing
(T *__autoreleasing*
)。这是因为Clang合理地假设,调用方并不总是需要释放这样的对象,所以它要求函数只分配自动释放对象。因此:
- (void)myMethod:(NSObject **)param {
*param = [NSObject new];
}
在ARC下变为:
- (void)myMethod:(NSObject *__autoreleasing*)param {
*param = [[NSObject new] autorelease];
}
这反过来又对这种方法的参数提出了特殊要求,因此在通常情况下,您实际上只是将一些(强保留)对象传递给函数:
NSObject *obj;
[self myMethod:&obj];
ARC实际上创建了一个临时的自动释放参数:
NSObject *__strong obj = nil;
NSObject *__autoreleasing tmp = obj;
[self myMethod:&tmp];
obj = [tmp retain];
是什么问题…
如果不是(间接)传递强保留指针,而是传递自己的间接指针,ARC不会在两者之间创建任何临时指针:
NSObject *__autoreleasing obj;
NSObject *__autoreleasing *objPtr = &obj;
[self myMethod:objPtr];
表示对象"返回";myMethod:
不再被保留,因此当当前的自动释放池被排干时将被破坏。如果传递具有相同语义的参数,则同样成立:
- (void)anotherMethod:(NSObject **)param {
[self myMethod:param];
}
因此,如果出于某种原因,您决定用自动释放块包装myMethod:
的调用,那么这里的代码最终会得到一个僵尸对象:
- (void)anotherMethod:(NSObject **)param {
@autoreleasepool {
[self myMethod:param]; // object was created and assigned to a autoreleasing pointer
} // ref. count of the object reached zero. `*param` refers to a released object
}
如果用块封装调用,也可能发生同样的
- (void)anotherMethod:(NSObject **)param {
void(^block)(void) = ^{
// "Block captures an autoreleasing out-parameter, which may result in use-after-free bugs" warning appears
[self myMethod:param];
};
block();
}
对于这个特定的实现,不会发生任何问题,您可以通过显式地提供间接指针__autoreleasing
限定符(通过此限定符,您可以告知Clang您很清楚可能的后果)来沉默错误:
- (void)anotherMethod:(NSObject *__autoreleasing*)param {
void(^block)(void) = ^{
[self myMethod:param];
};
block();
}
但是现在你必须非常小心,因为block是第一方对象,它可以在任何地方被保留和调用,并且有无数的场景会产生额外的自动释放池。例如,下面的代码将产生相同的zombie-object错误:
- (void)anotherMethod:(NSObject *__autoreleasing*)param {
void(^block)(void) = ^{
[self myMethod:param];
};
... some code here ...
@autoreleasepool {
block();
}
}
如果自动释放池正好在块体中,则相同:
- (void)anotherMethod:(NSObject **)param {
void(^block)(void) = ^{
@autoreleasepool {
[self myMethod:param];
}
};
block();
}
话虽如此,Clangs不会警告错误(在您的情况下,这实际上很明显,因为您用@autoreleasepool
块包装了块体),它只是希望您仔细检查您是否意识到可能存在的问题(正如您所看到的,仍然有可能实现这样的事情,但如果出现所有错误,您将很难跟踪它们)。
我该如何修复它?
这取决于你对"固定"的定义。您可以从块体中删除自动释放池块,并显式地限定__autoreleasing
参数(只要它只是在方法中的同一线程中调用):
- (BOOL)register:(NSString *)param error:(NSError *__autoreleasing*)errPtr {
....
dispatch_block_t block = ^{
....
[self registerWithElements:elements error:errPtr];
};
block();
....
}
或者你可以介绍另一个"本地的";变量来捕获并在块中传递它:
- (BOOL)register:(NSString *)param error:(NSError **)errPtr {
....
__block NSError *err;
dispatch_block_t block = ^{
@autoreleasepool {
....
[self registerWithElements:elements error:&err];
}
};
block();
*errPtr = err;
....
}
这再次暗示该块在方法中被同步调用,但不一定在相同的自动释放池块中。如果您希望存储块以供以后使用,或者异步调用它,那么您将需要另一个具有延长生存期的NSError
变量来在块中捕获。