Objective-C Block内存布局与实现原理详解
1. Block内存布局深度解析在编程领域Block是一种特殊的语法结构它允许我们将代码块作为参数传递或存储在变量中。理解Block的内存布局对于编写高效、安全的代码至关重要。今天我们就来彻底拆解Block在内存中的组织方式。Block本质上是一个对象它包含了执行代码和捕获的变量。在内存中Block由多个部分组成每个部分都有其特定的作用。我们先来看一个简单的Block示例void (^myBlock)(void) ^{ NSLog(This is a block); };这个看似简单的Block在内存中其实有着复杂的结构。接下来我们将从底层角度分析Block的内存布局。2. Block内存布局的核心结构2.1 Block的基本内存布局一个Block在内存中的典型布局包含以下几个关键部分Block描述符(Block Descriptor)捕获的变量(Captured Variables)函数指针(Invoke Function Pointer)标志位(Flags)在64位系统上一个Block的头部通常占用24字节的内存空间。我们可以用以下结构体来表示struct Block_layout { void *isa; volatile int32_t flags; int32_t reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; // 捕获的变量会跟随在这里 };2.2 各组成部分详解2.2.1 isa指针每个Block对象都包含一个isa指针指向它的类。这与其他Objective-C对象的行为一致。根据Block的类型不同isa指针可能指向_NSConcreteGlobalBlock全局Block_NSConcreteStackBlock栈Block_NSConcreteMallocBlock堆Block2.2.2 flags字段flags字段包含了Block的各种属性信息常见的标志位包括enum { BLOCK_HAS_COPY_DISPOSE (1 25), // 有copy和dispose辅助函数 BLOCK_HAS_CTOR (1 26), // 有C构造函数 BLOCK_IS_GLOBAL (1 28), // 是全局Block BLOCK_HAS_STRET (1 29), // 有stret返回值 BLOCK_HAS_SIGNATURE (1 30) // 有签名信息 };2.2.3 invoke函数指针invoke是一个函数指针指向Block的实现代码。当Block被调用时实际上就是通过这个指针来执行对应的函数。2.2.4 descriptor描述符descriptor指针指向Block的描述信息包含以下内容struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (*copy)(void *dst, void *src); void (*dispose)(void *); const char *signature; };3. Block的三种内存类型3.1 全局Block(NSGlobalBlock)全局Block具有以下特点不捕获任何外部变量生命周期与程序相同存储在程序的全局数据区多次使用同一个全局Block实际上是使用同一个对象示例void (^globalBlock)(void) ^{ NSLog(This is a global block); };3.2 栈Block(NSStackBlock)栈Block的特点捕获了外部变量存储在栈上生命周期与所在函数相同函数返回后栈Block就无效了示例int a 10; void (^ __weak stackBlock)(void) ^{ NSLog(This is a stack block: %d, a); };注意在ARC环境下编译器会自动将栈Block复制到堆上所以很难直接观察到栈Block。3.3 堆Block(NSMallocBlock)堆Block的特点由栈Block通过copy操作创建存储在堆上需要手动管理内存(在MRC下)可以跨函数使用示例int b 20; void (^heapBlock)(void) [^{ NSLog(This is a heap block: %d, b); } copy];4. Block的内存管理4.1 Block的copy操作Block的copy操作会根据不同类型有不同的行为全局Blockcopy操作什么也不做直接返回原Block栈Blockcopy操作会将Block从栈复制到堆上堆Blockcopy操作会增加引用计数在MRC环境下我们需要手动管理Block的内存// MRC下 void (^block)(void) [^{ /* ... */ } copy]; // 使用完后需要release [block release];在ARC环境下编译器会自动插入适当的copy/release调用。4.2 Block的循环引用问题由于Block会强引用它捕获的对象因此容易造成循环引用。常见解决方案使用__weak修饰符__weak typeof(self) weakSelf self; self.block ^{ [weakSelf doSomething]; };使用__block修饰符(仅限MRC)__block id blockSelf self; self.block ^{ [blockSelf doSomething]; blockSelf nil; // 打破循环 };5. Block的变量捕获机制5.1 自动变量捕获Block会捕获使用的外部变量但默认情况下是以const方式捕获的即在Block内部不能修改这些变量int value 10; void (^block)(void) ^{ // value 20; // 编译错误 NSLog(%d, value); };5.2 __block变量使用__block修饰的变量可以在Block内部修改__block int mutableValue 10; void (^block)(void) ^{ mutableValue 20; // 可以修改 NSLog(%d, mutableValue); };在底层__block变量会被包装成一个结构体struct __Block_byref_mutableValue_0 { void *__isa; __Block_byref_mutableValue_0 *__forwarding; int __flags; int __size; int mutableValue; };5.3 对象类型的捕获当Block捕获对象时会根据修饰符不同而有不同的行为默认是强引用id obj [[NSObject alloc] init]; void (^block)(void) ^{ NSLog(%, obj); // 强引用obj };使用__weak修饰符__weak id weakObj obj; void (^block)(void) ^{ NSLog(%, weakObj); // 弱引用obj };6. Block的底层实现原理6.1 Block的编译转换编译器会将Block转换为一个结构体和一个函数。例如int a 10; void (^block)(void) ^{ NSLog(%d, a); };会被转换为类似下面的代码struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0 *Desc; int a; }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a __cself-a; NSLog(%d, a); }6.2 Block的调用机制当调用一个Block时实际上是调用了Block结构体中的FuncPtr函数指针并将Block自身作为参数传递// block-invoke(block, ...);这就是为什么在Block内部可以访问到捕获的变量 - 因为它们都存储在Block结构体中。7. 实际应用中的注意事项7.1 性能优化建议避免在循环中创建Block特别是捕获了大量变量的Block对于不捕获变量的Block使用全局Block合理使用__weak避免不必要的强引用7.2 调试技巧使用po [block copy]可以在lldb中查看Block的详细信息通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc file.m命令可以查看Block的C实现使用Instruments的Allocations工具可以跟踪Block的内存分配情况7.3 常见问题排查EXC_BAD_ACCESS错误可能是使用了已经释放的栈Block内存泄漏检查是否有循环引用变量值不符合预期确认变量捕获方式是否正确8. 不同语言中的Block实现对比8.1 Objective-C与Swift闭包比较虽然Swift的闭包与Objective-C的Block概念相似但在实现上有一些差异Swift闭包不区分全局/栈/堆闭包Swift的捕获列表语法更明确Swift闭包默认捕获的变量是可变的8.2 C lambda表达式C11引入的lambda表达式与Block类似但有以下区别lambda可以按值或按引用捕获变量lambda可以有模板参数lambda没有自动的内存管理9. 高级话题Block的ABI与跨平台兼容性Block的ABI(应用程序二进制接口)在不同平台上有细微差异iOS/macOS使用统一的Block ABI其他平台可能需要特殊的编译器标志跨语言调用需要注意内存管理和调用约定在编写跨平台代码时需要特别注意Block的内存管理方式和调用约定。10. 实战案例自定义Block内存分配器对于需要高性能的场景我们可以实现自定义的Block内存分配器#import Block.h typedef void (^CustomBlock)(void); CustomBlock CreateCustomBlock(void (^original)(void)) { // 获取原始Block的大小 size_t size Block_size(original); // 分配内存 CustomBlock newBlock Block_copy(original); // 自定义初始化 // ... return newBlock; }这种技术可以用于特殊的内存管理策略或性能优化场景。

相关新闻

VRM4U插件在虚幻引擎中的表情控制:从BlendShape到动画蓝图实战

VRM4U插件在虚幻引擎中的表情控制:从BlendShape到动画蓝图实战

1. 项目概述:当VRM模型在虚幻引擎中“活”起来如果你在虚幻引擎(Unreal Engine)里折腾过VRM格式的二次元角色模型,那你一定遇到过这个灵魂拷问:模型导进来了,骨骼也能动了,但角色的表情怎么还是…

2026/10/22 12:27:14 阅读更多 →
瑞芯微AndroidTool固件烧录工具:从原理到实战的完整指南

瑞芯微AndroidTool固件烧录工具:从原理到实战的完整指南

1. 项目概述:为什么你需要一个趁手的固件烧录工具?如果你手头有一台基于瑞芯微(Rockchip)芯片的安卓设备,无论是开发板、平板电脑还是智能盒子,那么“固件烧录”这个词对你来说一定不陌生。它就像是给设备做…

2026/10/16 10:57:30 阅读更多 →
步进电机原理、驱动与工程应用全解析:从STM32控制到Eplan设计

步进电机原理、驱动与工程应用全解析:从STM32控制到Eplan设计

1. 从“脉冲”到“步进”:一个被低估的精密执行者如果你拆开过一台老式的针式打印机,或者仔细观察过3D打印机的打印头运动,大概率会看到一个带着几根线、转动起来一顿一顿的电机。没错,那就是步进电机。在很多工程师和爱好者的第一…

2026/10/21 7:36:12 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/22 10:18:38 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/22 11:01:53 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/22 9:44:47 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/22 10:42:00 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/22 10:40:08 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/22 10:56:37 阅读更多 →

日新闻

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/22 1:47:45 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/21 21:32:28 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/22 5:12:39 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:50:56 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 21:51:55 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 16:24:48 阅读更多 →