ios - EXC_BAD_ACCESS 在 enumerateObjectsUsingBlock 中设置传递回写错误

标签 ios automatic-ref-counting objective-c-blocks

以下代码在尝试设置*错误时会导致EXC_BAD_ACCESS

- (void)triggerEXC_BAD_ACCESS
{
    NSError *error = nil;
    [self doSetErrorInBlock:&error];
}

- (void)doSetErrorInBlock:(NSError * __autoreleasing *)error
{
    [@[@(0)] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        *error = [NSError errorWithDomain:@"some.domain" code:100 userInfo:nil]; // <--- causes EXC_BAD_ACCESS
    }];
}

但是,我不确定为什么会发生 EXC_BAD_ACCESS

用以下函数替换 enumerateObjectsUsingBlock: 调用,该函数尝试重现 enumerateObjectsUsingBlock: 的函数签名,将使函数triggerEXC_BAD_ACCESS运行无错误:

- (void)doSetErrorInBlock:(NSError * __autoreleasing *)error
{
    [self runABlock:^(id someObject, NSUInteger idx, BOOL *anotherWriteback) {
        *error = [NSError errorWithDomain:@"some.domain" code:100 userInfo:nil]; // <--- No crash here
    }];
}

- (void)runABlock:(void (NS_NOESCAPE ^)(id obj, NSUInteger idx, BOOL *stop))block
{
    BOOL anotherWriteback = NO;
    block(@"Some string", 0, &anotherWriteback);
}

不确定我是否遗漏了有关 ARC 在这里如何工作的任何信息,或者它是否特定于我正在使用的 Xcode 版本(Xcode 12.2)。

最佳答案

我无法重现 -doSetErrorInBlock: 中的崩溃,但我可以在 -triggerEXC_BAD_ACCESS 中重现崩溃在调试节点中使用“-[NSError keep]:消息发送到已释放的实例”(我不确定是否是由于 NSZombie 或其他一些调试选项所致)。

原因是*error-doSetErrorInBlock:类型为NSError * __autoreleasing ,并实现-[NSArray enumerateObjectsUsingBlock:] (这是闭源的,但可以检查程序集)恰好在 block 的执行内部有一个自动释放池。一个对象指针是 __autoreleasing意味着我们不保留它,并且我们假设它通过被某个自动释放池保留而处于事件状态。这意味着将某些内容分配给 __autoreleasing 是不好的。自动释放池内的变量,然后在自动释放池结束后尝试访问它,因为自动释放池的末尾可能已释放它,因此您可能会留下一个悬空指针。 This section ARC 规范说:

It is undefined behavior if a non-null pointer is assigned to an __autoreleasing object while an autorelease pool is in scope and then that object is read after the autorelease pool’s scope is left.

崩溃消息说它试图保留它的原因是因为当您尝试将“指向 __strong 的指针”(例如 &error 中的 -triggerEXC_BAD_ACCESS )传递给类型参数时会发生什么“指向 __autoreleasing 的指针”(例如 -doSetErrorInBlock: 的参数)。从this section可以看出根据 ARC 规范,会发生“回写传递”过程,其中创建 __autoreleasing 类型的临时变量,分配 __strong 的值变量给它,进行调用,然后分配 __autoreleasing 的值变量返回__strong变量,所以你的 triggerEXC_BAD_ACCESS方法实际上是这样的:

NSError *error = nil;
NSError * __autoreleasing temporary = error;
[self doSetErrorInBlock:&temporary];
error = temporary;

最后一步将值赋回 __strong变量执行保留,此时它遇到已释放的实例。

如果我更改-runABlock:,我可以在第二个示例中重现相同的崩溃至:

- (void)runABlock:(void (NS_NOESCAPE ^)(id obj, NSUInteger idx, BOOL *stop))block
{
    BOOL anotherWriteback = NO;
    @autoreleasepool {
        block(@"Some string", 0, &anotherWriteback);
    }
}

你不应该真正使用__autoreleasing在您编写的新方法中。 __strong好多了,因为强引用可以确保您不会意外地遇到悬空引用和类似的问题。主要原因__autoreleasing存在是因为回到手动引用计数时代,没有显式的所有权限定符,并且“约定”是保留计数不会传入或传出方法,因此从方法返回的对象(包括使用指针返回的对象) out-parameter)将被自动释放而不是保留。 (这些方法将负责确保该对象在方法返回时仍然有效。)并且由于您的程序可以在不同的操作系统版本上使用,因此它们无法更改新操作系统版本中 API 的行为,因此它们被困在这个“指向 __autoreleasing 的指针”类型。但是,在您自己用 ARC 编写的方法(它确实具有显式所有权限定符)中,该方法仅由您自己的 ARC 代码调用,请务必使用 __strong 。如果您使用 __strong 编写方法,它不会崩溃( by default 对象指针的指针被解释为 __autoreleasing ,因此您必须显式指定 __strong ):

- (void)doSetErrorInBlock:(NSError * __strong *)error
{
    [@[@(0)] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        *error = [NSError errorWithDomain:@"some.domain" code:100 userInfo:nil];
    }];
}

如果您出于某种原因坚持采用 NSError * __autoreleasing * 类型的参数,并且想要做与您所做的相同的事情,但安全地,您应该使用 __strong block 的变量,并且仅将其分配到 __autoreleasing最后:

- (void)doSetErrorInBlock:(NSError * __autoreleasing *)error
{
    __block NSError *result;
    [@[@(0)] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        result = [NSError errorWithDomain:@"some.domain" code:100 userInfo:nil];
    }];
    *error = result;
}

关于ios - EXC_BAD_ACCESS 在 enumerateObjectsUsingBlock 中设置传递回写错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66318713/

相关文章:

objective-c - ARC 属性的新属性

objective-c - 如何在 Interface Builder 中更改 UIButton 的背景图像并保留其形状?

iOS - EXC_BAD_ACCESS 代码 = 1 UIWebView 在(方法混合)初始化后崩溃

objective-c - 启用 ARC 后出现奇怪的内存问题

ios - 何时以及何时不在 Objective-C 中使用 __block?

iphone - 在 Objective C 中使用 block 编程进行内存管理

ios - 点击时禁用 UICollectionViewCell,并随机化单元格

ios - 使用 ARToolkit ios+sdk 进行触摸,将 3d AR 对象旋转 360 度

iphone - iPhone-如何从设备读取书籍文件

objective-c - dipatch_async 释放局部变量