EXC_BAD_ACCESS 在 enumerateObjectsUsingBlock 中设置通过写回错误

如何解决EXC_BAD_ACCESS 在 enumerateObjectsUsingBlock 中设置通过写回错误

以下代码在尝试设置 EXC_BAD_ACCESS 时导致 *error

- (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,BOOL *anotherWriteback) {
        *error = [NSError errorWithDomain:@"some.domain" code:100 userInfo:nil]; // <--- No crash here
    }];
}

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

不确定我是否遗漏了 ARC 在这里的工作原理,或者它是否特定于我使用的 Xcode 版本(Xcode 12.2)。

解决方法

我无法在 -doSetErrorInBlock: 中重现崩溃,但我可以在调试节点中使用“-[NSError retain]: message sent to deallocated instance”重现 -triggerEXC_BAD_ACCESS 中的崩溃(我不是确定是由于 NSZombie 还是其他一些调试选项)。

其原因是 *error 中的 -doSetErrorInBlock: 具有类型 NSError * __autoreleasing,以及 -[NSArray enumerateObjectsUsingBlock:] 的实现(它是封闭源代码,但可以检查程序集) 恰好在内部有一个围绕块执行的自动释放池。 __autoreleasing 的对象指针意味着我们不保留它,并且我们假设它是活的,因为它被某个自动释放池保留了。这意味着将某些东西分配给自动释放池中的 __autoreleasing 变量,然后在自动释放池结束后尝试访问它是不好的,因为自动释放池的末尾可能已经释放了它,所以你可以离开带有悬空指针。 ARC 规范的 This section 说:

如果一个非空指针被分配给一个未定义的行为 __autoreleasing 对象,而自动释放池在范围内并且 然后在离开自动释放池的范围后读取该对象。

崩溃消息说它正在尝试保留它的原因是因为当您尝试传递“指向 __strong 的指针”时会发生什么(例如 &error 中的 -triggerEXC_BAD_ACCESS ) 到“指向 __autoreleasing 的指针”类型的参数(例如 -doSetErrorInBlock: 的参数)。正如您从 ARC 规范的 this section 中看到的那样,发生了一个“pass-by-writeback”过程,在该过程中,他们创建了一个 __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",&anotherWriteback);
    }
}

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

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res