Objective-c 初阶——异常处理(try-catch)

一、@try/@catch/@throw/@finally 执行顺序

void doSomething() {NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];@try {// 这一步抛异常[self riskyMethod]; } @catch (NSException *e) {@throw;  // 把异常继续往上抛} @finally {// ❗ 注意:这里的 finally 会 **立刻执行**[pool release]; // 🔥🔥🔥 外层 pool 提前 pop 了}// ❌ 如果你此时还没处理完那个 exception,它里面的对象已经被释放了
}1. riskyMethod 抛出异常
2. 进入 @catch 块
3. @catch 里调用了 @throw(重新抛出)
4. ❗ 根据编译器实现,@finally 会 **立刻执行**⬅️ 在这个“再抛之前”,@finally 就执行了
5. @finally 执行时,释放了 autorelease pool
6. ❌ 异常对象(NSException 实例)可能在 autorelease pool 中,它被提前释放了
7. 异常还没被真正交到上层 catch,就已经是 zombie 了

二、try-catch 内存管理(坑)

1、普通内存泄漏

1.1. 如何泄漏

- (void)doSomething {NSMutableArray *anArray = [[NSMutableArray alloc] initWithCapacity:0];@try {[self doSomethingElse:anArray]; // 假设一定会抛错  } @catch(...) {@throw;}[anArray release];
}

就像这种情况,我在 catch 里又重新抛异常,那么就会导致 [anArray release]; 就不会被执行了,所以 anArray 就会内存泄漏了。

1.2. 解决方案

- (void)doSomething {NSMutableArray *anArray = [[NSMutableArray alloc] initWithCapacity:0];@try {[self doSomethingElse:anArray]; // 假设一定会抛错  } @catch(...) {@throw;} @finally {[anArray release];}
}

2、提前释放 auto release pool 引起的内存泄漏

2.1. 如何泄漏

- (void)doSomething {NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];NSException *ex = [NSException exceptionWithName:@"MyError" reason:nil userInfo:nil]; // 创建 NSException,retain 一次,retainCount + 1@throw ex; // 由 runtime retain 1 次,retainCount + 2[pool release]; // 不会执行
}@try {[someObject doSomething]; // 抛出 ex
} @catch (NSException *e) {@throw; // 再次抛出 ex,但这是 rethrow, retainCount 不会加 1
} @finally {[outerPool release]; // 释放 ex 所在 autorelease pool,retainCount - 1;// 因为 ex 是通过 unwind 找到当前栈帧的,且当前栈帧有 @finally,// 所以会调一次 runtime cleanup 来清理对象,retainCount - 1;// 此时 ex 的 retainCount 为 0,ex 被 dealloc
}

因为 doSomething: 函数内部创建了一个 auto release pool;而且外层又有一个 auto release pool,所以这两个 auto release pool 是这样的:

然后 ex 的引用计数看注释就可以了。顺带提一下,只要是被 unwind 到当前栈帧且当前栈帧有 @finally,系统就会在 finally 块里调 cleanup 函数,给当前所有对象的 retain count 减 1.

那什么时候会被 unwind 呢?其实就是当异常对象被 throw 或当前函数栈帧无法捕获异常时,系统就会调unwind 来一个个函数栈帧往上找。只要当前栈帧(没有 throw 的栈帧)的 catch 能捕获到异常且 catch 不再重新抛出异常,unwind 就不会继续被调。

 2.2. 解决方案

- (void)doSomething {NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];NSException *ex = [NSException exceptionWithName:@"MyError" reason:nil userInfo:nil]; @throw ex; [pool release]; // 不会执行
}@try {[someObject doSomething]; // 抛出 ex
} @catch (NSException *e) {[e retain];@throw; 
} @finally {[outerPool release]; 
}

是的,就是给 e 的 retainCount 多加 1 就可以了。这样就可以给整个异常对象的引用计数加 1,不让他被 dealloc 掉。 

三、性能对比:32 位的 try-catch VS 64 位的 try-catch

先提前下个结论:根据苹果开发者文档,其实 32 位跟 64 位的 try-catch 各有利弊。因为苹果针对  64 位情况重新实现了 objective-c 的异常处理机制,也就是说采用了 cpp 的 zero-cost 的异常处理机制;因此,32 位的 try-catch 在 try 的部份性能比 64 位的 try 部份差;而 64 位的 throw 部份会比 32 位的 throw 部份差。下面我会分别讲这两部分性能差异的原理。不过先叠个甲,里面的伪代码实现都是用于逻辑自洽的,语法上难免会有错误。

1、32 位下 objective-c 的异常处理机制

其实在 32 位下 objective-c 的异常处理机制是这样的:每进入一个 @try 块,都会先创建一个异常栈帧(不是函数的栈帧),把当前 @try 代码块的地址写到 jmp_buf env 里,然后再调 setjmp 函数来标记 longjmp() 应该跳到的地方。

对于每一个 throw 函数,都会调 longjmp() 函数,然后让程序的 pc 指针指向当前异常栈帧的 setjmp(frame.env) 处。因为 setjmp(frame.env) 此时返回值不是 0,所以跳到 catch 执行……然后一直沿着 try 的调用链往上找 catch。

typedef struct ExceptionFrame {jmp_buf env;struct ExceptionFrame *prev;const char *handledType; // 这个 try 块能处理的异常类型Exception *caughtException; // 用于传值给 catch
} ExceptionFrame;__thread ExceptionFrame *topFrame = NULL; // 每个线程一份
// try 的伪代码实现
#define TRY(type) do { \ExceptionFrame frame; \frame.prev = topFrame; \frame.handledType = type; \frame.caughtException = NULL; \topFrame = &frame; \if (setjmp(frame.env) == 0)#define CATCH(x) else for (Exception *x = frame.caughtException; x != NULL; x = NULL)#define END_TRY \topFrame = frame.prev; \
} while (0)// throw 的伪代码实现
#define THROW(e) do { \currentException = e; \ExceptionFrame *frame = topFrame; \while (frame) { \if (strcmp(frame->handledType, e->type) == 0 || strcmp(frame->handledType, "*") == 0) { \frame->caughtException = e; \topFrame = frame->prev; \longjmp(frame->env, 1); \} \frame = frame->prev; \} \fprintf(stderr, "Uncaught exception: %s\n", e->message); \abort(); \
} while (0)
TRY {// 这里写 try 块的内容maybeDangerousFunction();THROW(&someException);
} CATCH(e) {} END_TRY;

2、64 位下 objective-c 的异常处理机制

但是在 64 位下 objective-c 的异常处理用的是 zero-cost 那套,也就是每进入一个 try 块时并不会创建一个 exception frame。但一旦遇到 throw 时,就会从当前函数栈帧开始,找每个栈帧的匹配的 catch 块,找不到就再去上一个函数栈帧找。

struct StackFrame {StackFrame *prev;          // 栈上的上一个帧FunctionMeta *functionMeta; // 指向 LSDA + handler 信息void *pc;                  // 当前执行地址 (程序计数器)
};struct Exception {const char *type;const char *message;
};typedef struct {char *type;                   // 异常类型void (*handler)(Exception*);  // catch handler 跳转landing padvoid (*cleanup)(void);        // cleanup handler,例如 finallyint hasCleanup;               // 是否有 cleanup
} CatchClause;struct FunctionMeta {CatchClause *clauses; // 存 catch 代码块地址的数组,一个 catch 对应一个 clauseint clauseCount;void (*personality)(StackFrame *, Exception *);
};
// 默认的 personality 函数,带 cleanup 处理
void defaultPersonality(StackFrame *frame, Exception *e) {int handled = 0; // 该层栈帧的 catch 能否捕获异常,可以为 1,不可以为 0// 先查找 catch clausefor (int i = 0; i < frame->functionMeta->clauseCount; ++i) {CatchClause *clause = &frame->functionMeta->clauses[i];if (strcmp(clause->type, e->type) == 0) {// 找到匹配的 catchclause->handler(e);  // 执行 catch handler(landing pad)handled = 1;break;}}// 调用 cleanup handler(无论 catch 是否找到都会调用 cleanup)for (int i = 0; i < frame->functionMeta->clauseCount; ++i) {CatchClause *clause = &frame->functionMeta->clauses[i];if (clause->hasCleanup && clause->cleanup != nullptr) {clause->cleanup(); // 执行 cleanup,比如 finally}}if (!handled) {// 当前栈帧不能处理异常,继续向上 unwindunwind(frame->prev, e);}
}// throw 异常时当前栈帧直接调 unwind,可以看成 defaultPersonality 是私有的
void unwind(StackFrame *frame, Exception *e) {if (!frame) {printf("Uncaught exception: %s\n", e->message);abort();}frame->functionMeta->personality(frame, e);
}

unwind 执行什么呢?就是先看看当前栈帧有没有符合类型的 catch 可以捕获异常;然后在看看当前栈帧有没有 finally,有的话就调 cleanup();然后如果当前栈帧可以捕获异常,不继续调 unwind ,否则继续调上一个栈帧的 unwind.

3、性能差异原因

所以 32 位下 oc 的 try 比 64 的 try 性能消耗更大是因为 32 位下 oc 会给每一个 try 块创建一个叫 jmp_buf env 的东西,而且还会调一次 setjmp() ;但 64 位对每一个 try 块并不会做任何事,所以 32 位下 try 块会比 64 位需要更多的内存。

那 64 位的 throw 比 32 位的 throw 性能更差是因为 32 位下用的是 exception frame + longjmp() 来跳转,每一跳肯定可以跳到最近有 catch 的函数栈帧中,所以 32 位找对应 catch 所花的时间是比 64 位一个一个栈帧找要快的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/91309.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/91309.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络:(十二)传输层(上)运输层协议概述

计算机网络&#xff1a;&#xff08;十一&#xff09;多协议标记交换 MPLS前言一、运输层的作用二、基于端口的复用和分用功能三、屏蔽作用四、可靠信道与不可靠信道五、运输层的两个主要协议前言 前面我们讲解了计算机网络中网络层的相关知识&#xff0c;包括网络层转发分组的…

一场关于电商零售增长破局的深圳探索

“电商AI&#xff0c;不再是选择题”2025年&#xff0c;电商行业正面临流量成本攀升、用户留存率下降、供应链协同效率低等核心困境&#xff0c;传统数字化工具已难以满足精细化运营需求。在此背景下&#xff0c;百度智能云正加速布局电商领域&#xff0c;为零售企业提供从基础…

当非洲爱上“中国制造”:如何赢在起跑线

非洲大陆的消费浪潮正以前所未有的速度奔涌。2025年前五个月&#xff0c;中非贸易额同比暴涨12.4%&#xff0c;创下历史新高。在这片拥有14亿人口的土地上&#xff0c;60%是30岁以下的年轻人&#xff0c;城镇化浪潮席卷、中产阶级快速崛起&#xff0c;从家电、汽车到建材、电子…

vLLM(3)vllm在线启动集成openweb-ui

文章目录**步骤 1: 启动 vLLM 服务****方式 1: 直接命令行启动****方式 2: Docker 启动****步骤 2: 配置 Open WebUI 连接 vLLM****方法 1: 修改 Open WebUI 环境变量****方法 2: 通过 docker-compose.yml 部署****步骤 3: 在 Open WebUI 中添加模型****验证是否成功****常见问…

Python----大模型(基于Agent的私人AI助理项目)

开发一个智能的问答系统&#xff0c;该系统支持用户聊天&#xff0c;传输文件。通过自然语言处理技术&#xff0c;机器人能够理解用户的意图。机器人将利用互联网搜索引擎来补充信息&#xff0c;确保用户能够获得全面且准确的回答。 一、web ui界面 我们采用gradio来编写的ui界…

Python爬虫实战:研究scrapely库相关技术构建电商数据提取系统

1. 引言 在当今数字化时代,网络上蕴含着海量的有价值信息。如何从这些非结构化的网页中自动提取出结构化的数据,成为了数据挖掘和信息检索领域的重要研究课题。网络爬虫作为一种自动获取网页内容的技术,被广泛应用于信息收集、数据分析等领域。然而,网页结构的多样性和复杂…

Orange的运维学习日记--18.Linux sshd安全实践

Orange的运维学习日记–18.Linux sshd安全实践 文章目录Orange的运维学习日记--18.Linux sshd安全实践场景模拟sshd配置需求&#xff1a;参数表格&#xff1a;MOTD警告定期备份SSH密钥与配置登录防护&#xff1a;fail2ban测试与日志场景模拟 你是某金融科技公司的Linux运维工程…

聚观早报 | 德赛电池自主研发主动安全AI电芯;华为首展线下昇腾384超节点;蔚来纯电小车萤火虫已交付一万辆

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。整理丨肖羽7月28日消息德赛电池自主研发主动安全AI电芯华为首展线下昇腾384超节点蔚来纯电小车萤火虫已交付一万辆京东已成立智能机器…

python可视化:按序号展示社团星级分布 - 热力图样式

目录 关键代码&#xff1a; 运行结果&#xff1a; 关键代码&#xff1a; # 4. 按序号展示社团星级分布 - 热力图样式 plt.subplot(2, 2, 4) # 创建星级映射为数值 star_mapping {五星:4, 四星:3, 三星:2, 星级入围:1} star_values df[答辩结果].map(star_mapping) plt.s…

智能家居平台服务端安装教程——东方仙盟部署

1. 准备工作 登录阿里云服务器&#xff1a;使用 SSH 工具&#xff08;如 ssh 命令&#xff09;登录到你的阿里云 Linux 服务器。确保服务器已安装并能正常运行&#xff0c;且网络连接正常。更新系统软件包&#xff1a;执行以下命令更新系统软件包到最新版本。 bash sudo apt …

360环视技术推荐的硬件平台:支持多摄像头与三屏异显的理想选择

在智能硬件不断升级的今天&#xff0c;360环视技术早已不只是豪华车型的专属。通过布置多路摄像头&#xff0c;并将其图像进行融合处理&#xff0c;360环视可以为系统提供车辆或设备周围的完整视野。 它不仅能够消除盲区&#xff0c;还能通过AI识别实现物体检测、避障判断、自动…

FFmpeg:因码流采集与封装不同步导致录制出来的MP4文件会出现黑屏、绿屏的问题

项目要求实时播放视频&#xff0c;并且需要支持播放中途可以录制视频。但是录制出来的文件会黑屏&#xff0c;过一段时间后正常显示。即&#xff1a;码流采集—>播放—>&#xff08;一段时间后&#xff09;录制MP4&#xff0c;黑屏出现的时间就在采集到录制之前。黑屏现象…

C 语言与 C++、Java、Python 等编程语言的区别

C 语言与 C、Java、Python 等编程语言在设计理念、特性和应用场景上存在显著差异&#xff0c;以下从核心区别、特性对比和适用场景三个维度详细解析&#xff1a;一、C 语言与 C 的核心区别C 是在 C 语言基础上发展而来&#xff08;最初名为 “C with Classes”&#xff09;&…

Apache Ignite 的分布式锁Distributed Locks的介绍

以下这段内容是关于 Apache Ignite 的分布式锁&#xff08;Distributed Locks&#xff09; 的介绍。这是一个非常重要的功能&#xff0c;用于在分布式系统中协调多个节点对共享资源的并发访问。 下面我们来一步步深入理解它。&#x1f3af; 一、一句话理解&#xff1a;什么是 I…

第十二天:C++ 标准库函数分类总结

C 标准库函数分类总结 数学函数&#xff08;<cmath>&#xff09; 基本运算函数 abs(x)&#xff1a;返回整数或浮点数的绝对值。int a abs(-5); // 返回 5fabs(x)&#xff1a;返回浮点数的绝对值。double b fabs(-3.14); // 返回 3.14fmod(x, y)&#xff1a;计算 x 除以…

Unity Standard Shader 解析(四)之ForwardAdd(简化版)

一、ForwardAdd// Additive forward pass (one light per pass)Pass{Name "FORWARD_DELTA"Tags { "LightMode" "ForwardAdd" }Blend [_SrcBlend] OneFog { Color (0,0,0,0) } // in additive pass fog should be blackZWrite OffZTest LEqualC…

第十九周-文档数据库MongoDB、消息队列和微服务

1. 完成redis单机安装&#xff0c;哨兵模式安装&#xff0c;主从安装&#xff0c;集群安装单机安装#安装依赖包 [rootcentos8~]#yum -y install gcc make jemalloc-devel #如果支持systemd需要安装下面包 [rootubuntu2204 ~]#apt update && apt -y install make gcc li…

C++中sizeof运算符全面详解和代码示例

sizeof 是 C 中的一个编译时运算符&#xff0c;用于获取对象或类型所占的字节数&#xff08;以 size_t 返回&#xff09;。它是掌握底层内存模型、结构体对齐、数组大小计算等的重要工具。1. 基本语法 sizeof(type) // 获取类型的大小 sizeof expression // 获取表达式结果…

内容中台:在一个地方管理多渠道内容

在数字化竞争愈演愈烈的今天&#xff0c;企业官网、社交平台、移动应用、邮件营销等渠道已成为品牌触达用户的关键接口。内容仍是连接企业与客户的核心资产。然而&#xff0c;内容创作与分发的复杂性持续攀升&#xff0c;多平台运营面临重复维护、更新不一致、资源冗余等诸多挑…

【刷题】东方博宜oj 1307 - 数的计数

样例输入&#xff1a; 6输出&#xff1a; 16 26 126 36 136原版是直接输出总数量&#xff0c;但我修改了一些&#xff0c;输出所有的新数。 #include <iostream> #include <vector> #include <string> using namespace std; //int c; void g(int num, string…