【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(一)
自动引用计数
- 前言
- alloc/retain/release/dealloc实现
- 苹果的实现
- autorelease
- autorelease实现
- 苹果的实现
- 总结
前言
此前,写过一遍对自动引用计数的简单学习,因此掠过其中相同的部分:引用计数初步学习
alloc/retain/release/dealloc实现
由于NSObject类的源码不公开,我们通过开源软件GNUstep来学习相关内容。
GNUstep是Cocoa框架的互换框架,虽然并不是与苹果Cocoa的实现方式完全相同,但是从使用者的角度来看二者的行为和实现方式是一样的,理解了GNUstep的源代码也相当于理解了苹果的Cocoa实现。
先来看alloc类方法。
id obj = [NSObject alloc];
上述调用NSObject类的alloc类方法在NSObject.m的源代码中的实现如下:
+ (id) alloc
{return [self allocWithZone: NSDefaultMallocZone()];
}+ (id) allocWithZone: (NSZone*) z
{return NSAllocateObject(self, 0, z);
}
上述代码使用了在allocWithZone类方法中,使用NSAllocateOject函数来分配对象。下面是这个函数的代码:
struct obj_layout {NSUInteger retained;
};inline id
NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)
{int size = 计算容纳对象所需内存大小;id new = NSZoneMalloc(zone, size);memset(new, 0, size);new = (id) &((struct obj_layout *) new)[1];
}
NSAllocateOject函数通过调用NSZoneMalloc函数来进行分配存放对象所需的内存空间,之后将该内存空间置0,最后返回作为对象而使用的指针。
NSZone是为了防止内存碎片化而引入的结构。对内存分配的区域本身进行多重化管理,根据使用对象的目的和大小分配内存,从而提高内存管理的效率。

因此简化NSZone方法后的alloc源代码如下:
// 定义 obj_layout 结构体,用于存储引用计数
struct obj_layout {NSUInteger retained;
};// 实现 alloc 方法
+ (id)alloc {// 计算对象所需的内存大小int size = sizeof(struct obj_layout) + class_getInstanceSize([self class]);// 分配内存并初始化为 0struct obj_layout *p = (struct obj_layout *)calloc(1, size);// 返回指向对象内存的指针(跳过 obj_layout)return (id)(p + 1);
}
alloc类方法使用obj_layout来保存对象的引用计数,记录在retain字段中,并将其写入对象内存头部,该对象全部置0后返回。alloc后返回的对象如图所示:

alloc后对象引用计数加一。下面是GNUstep源码:
-(NSUInterger) retainCount
{return NSextraRefCount (self) + 1;
}
inline NSUInteger
NSExtraRefCount(id anObject)
{return ((struct obj_layout *)anObject) [-1].retained;
}
这里需要由对象寻址找到对象内存头部,访问其中的retained变量。

retain方法使retained变量加1,release方法使retained变量减1。
下面是retain和release的源码:
retain源码:

retain的实例方法中是调用NSIncrementExtraRefCount函数,该函数的作用是使retained加1。并且为该变量超出最大值做出处理。
release源码:

release方法先调用NSDecrementExtraRefCountWasZero函数,该函数的作用是让retained一直减到0。减到0后调用dealloc方法。废弃该对象。
上述代码仅废弃由alloc分配的内存块。
苹果的实现
使用NSObject类的alloc方法时,调用以下方法和函数
- +alloc
- +allocWithZone
- class_createInstance
- calloc
这个调用过程与前文所讲GNUstep相似,先调用allocWithZone方法,在调用class_createInstance函数,最后通过调用calloc来分配内存块。
接下来看retainCount/retain/release实例方法如何实现:

每个方法都调用了同一个函数_CFDoExternRefOperation该函数的前缀“CF“表明,它们包含于Core Foundation框架源代码中。我们来看其简化后的源码:
int __CFDoExternRefOperation(uintptr_t op, id obj) {CFBasicHashRef table = 取得对象对应的散列表(obj);int count;switch(op) {case OPERATION_retainCount:count = CFBasicHashGetCountOfKey(table, obj);return count;case OPERATION_retain:CFBasicHashAddValue(table, obj);return obj;case OPERATION_release:count = CFBasicHashRemoveValue(table, obj);return 0 == count;}
}
该函数按retainCount/retain/release操作进行分发,调用不同的函数。他们的实例方法可能如下所示:
- (NSUInteger)retainCount {return (NSUInteger)__CFDoExternRefOperation(OPERATION_retain, self);
}
- (id)retain {return (id)__CFDoExternRefOperation(OPERATION_retain, self);
}
- (void)release {return __CFDoExternRefOperation(OPERATION_release, self);
}
可以从__CFDoExternRefOperation函数以及由此函数调用的各个函数名中看出,苹果的实现大概采用散列表(引用计数表)来管理引用计数。如图所示:

GNUstep和苹果在实现引用计数的保存上有所不同
- GNUstep将引用计数保存在对象占用内存头部的变量
少量代码即可完成,能够统一管理引用计数内存块与对象用内存块
- 苹果保存在引用计数表中记录
对象用内存块的分配无需考虑内存块头部。
引用计数表各记录中存有内存块地址,可从各个记录中追溯到各对象的内存块。
这一点在调试故障时非常有效。即使出故障导致对象占用的内存块损坏,但只要引用计数表没有破坏,就能够确认各块内存块的位置。

autorelease
autorelease是自动释放,虽然看上去像ARC,但实际上更类似于C语言中的自动变量(局部变量)的特性。
autorelease会像C语言的局部变量那样对待对象实例。当超出其作用域时,对象的release实例方法才被调用。但是不同的是,autorelease可以设定变量的作用域。具体使用方法如下:
- 生成并持有NSAutoreleasePool对象
- 调用已分配对象的autorelease方法
- 废弃NSAutoreleasePool方法
NSAutoreleasePool对象的生存周期相当于C语言变量的作用域。对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。如图所示:

源代码如下:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];id obj = [[NSObject alloc] init];[obj autoreless];[pool drain];
其中,[pool drain]方法等同于[obj release]。
在Cocoa框架中,相当于程序主循环的NSRunLoop或者在其他程序可运行的地方,对NSAutoreleasePool对象进行生成,持有,和废弃处理。

但是,只要不废弃NSAutoreleasePool对象,那么生成的对象就不能释放,因此会产生内存不足的现象。我们只需要自定义一个pool,在最后进行[pool drain]。
for (int i = 0; i < image.count; i++) {NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]];
/*产生autorelease对象
*/
[pool drain]
}
}
许多类方法也会返回autorelease对象,如NSMutableArray类的arrayWithCapacity类方法。
autorelease实现
我们先来看一下autorelease源码
-(id) autorelease
{[NSAutoreleasePool addObject:self];
}
该方法的本质就是调用NSAutoreleasePool对象的addObject类方法。
IMP Caching
当我们调用一个方法时,OC通过消息传递机制实现 :
- 查找方法名(selector)。
- 找到方法的实现(IMP,即函数指针)。
- 执行方法。
这个过程非常灵活,但是如果需要频繁的调用方法,如上文的autorelease方法,那么则会带来一定的性能开销。
为了减少这种开销,我们则采用IMP Caching技术:
- 在程序初始化时,预先查找并缓存方法的实现(IMP)。
- 在后续调用时,直接使用缓存的 IMP,避免重复查找。
通常情况下,IMP Caching的速度是普通方法的两倍。尤其是在频繁调用方法时。
现在我们看NSAutoreleasePool类的实现。由于该类的源代码比较复杂,因此我们假想一个简化的源代码学习:
+ (void) addObject:(id) anObj
{NSAutoreleasePool *pool = 取得正在使用的NSAutoreleasePool对象;if(pool != nil) {[pool addObject:anObj];} else {NSLog(@"NSAutoreleasePool对象非存在状态下调用autorelease");}
}
addObject类方法调用正在使用的NSAutoreleasePool对象的addObject实例方法。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
上述是被赋予pool变量的即为正在使用的NSAutoreleasePool对象。
如果嵌套生成或持有的NSAutoreleasePool对象,则会使用最内侧的对象。
NSAutoreleasePool *pool0 = [[NSAutoreleasePool alloc] init];
NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc] init];
NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];id obj = [[NSObject alloc] init];
[obj autorelease]; // obj 被添加到 pool2 中[pool2 drain]; // 释放 pool2 中的对象
[pool1 drain]; // 释放 pool1 中的对象
[pool0 drain]; // 释放 pool0 中的对象
上述代码中,obj会被添加到最内层的pool2中。当调用drain时,先释放pool2,再释放pool1和pool0。
我们继续看addObject实例方法的实现。
-(void) addObject:(id) anObj
{[array addObject:anObj];
}
GNUstep实现使用的是连接列表,这同在NSMutableArray对象中追加对象参数是一样的。
如果调用NSObject类的autorelease实例方法,该对象被追加到正在使用的NSAutoreleasePool对象中的数组里。
下面我们看drain实例方法
- (void)drain {[self dealloc];
}- (void)dealloc {[self emptyPool];[array release];
}- (void)emptyPool {for (id obj in array) {[obj release];}
}
该方法会释放pool中所有的对象。
苹果的实现
class AutoreleasePoolPage {
public:static inline void *push() {相当于生成或持有NSAutoreleasePool类对象;}// 释放自动释放池static inline void pop(void *token) {相当于废弃NSAutoreleasePool类对象;releaseAll();}static inline id autorelease (id obj){相当于NSAutoreleasePool类的addObject类方法AutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的AutoreleasePoolPage实例;autoreleasePoolPage->add(obj);}// 将对象添加到内部数组id *add(id obj) {}// 释放内部数组中的所有对象void releaseAll() {// 遍历内部数组,调用每个对象的 release 方法for (id obj : internalArray) {[obj release];}// 清空数组internalArray.clear();}// 创建一个新的自动释放池
void *objc_autoreleasePoolPush(void) {return AutoreleasePoolPage::push();
}// 释放自动释放池
void objc_autoreleasePoolPop(void *ctxt) {AutoreleasePoolPage::pop(ctxt);
}// 将对象添加到自动释放池
id objc_autorelease(id obj) {return AutoreleasePoolPage::autorelease(obj);
}
我们还可以使用NSAutoreleasePool类中的调试用非公开类方法showPools来确认已被autorelease的对象的状况。
autorelease NSAutoreleasePool对象会发生异常。
总结
对引用计数相关方法的实现原理进行简单了解。对比GNUstep和苹果对统一操作不同实现方法优劣的好坏。如引用计数的保存。
相关文章:
【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(一)
自动引用计数 前言alloc/retain/release/dealloc实现苹果的实现 autoreleaseautorelease实现苹果的实现 总结 前言 此前,写过一遍对自动引用计数的简单学习,因此掠过其中相同的部分:引用计数初步学习 alloc/retain/release/dealloc实现 由于…...
Python爬虫第15节-2025今日头条街拍美图抓取实战
目录 一、项目背景与概述 二、环境准备与工具配置 2.1 开发环境要求 2.2 辅助工具配置 三、详细抓取流程解析 3.1 页面加载机制分析 3.2 关键请求识别技巧 3.3 参数规律深度分析 四、爬虫代码实现 五、实现关键 六、法律与道德规范 一、项目概述 在当今互联网时代&a…...
智慧城市像一张无形大网,如何紧密连接你我他?
智慧城市作为复杂巨系统,其核心在于通过技术创新构建无缝连接的网络,使物理空间与数字空间深度融合。这张"无形大网"由物联网感知层、城市数据中台、人工智能中枢、数字服务入口和安全信任机制五大支柱编织而成,正在重塑城市运行规…...
网络安全·第四天·扫描工具Nmap的运用
今天我们要介绍网络安全中常用的一种扫描工具Nmap,它被设计用来快速扫描大型网络,主要功能包括主机探测、端口扫描以及版本检测,小编将在下文详细介绍Nmap相应的命令。 Nmap的下载安装地址为:Nmap: the Network Mapper - Free Se…...
黑龙江 GPU 服务器租用:开启高效计算新征程
随着人工智能、深度学习、大数据分析等技术的广泛应用,对强大计算能力的需求日益迫切。GPU 服务器作为能够提供卓越并行计算能力的关键设备,在这一进程中发挥着至关重要的作用。对于黑龙江地区的企业、科研机构和开发者而言,选择合适的 GPU 服…...
大数据面试问答-HBase/ClickHouse
1. HBase 1.1 概念 HBase是构建在Hadoop HDFS之上的分布式NoSQL数据库,采用列式存储模型,支持海量数据的实时读写和随机访问。适用于高吞吐、低延迟的场景,如实时日志处理、在线交易等。 RowKey(行键) 定义…...
SparseDrive---论文阅读
纯视觉下的稀疏场景表示 算法动机&开创性思路 算法动机: 依赖于计算成本高昂的鸟瞰图(BEV)特征表示。预测和规划的设计过于直接,没有充分利用周围代理和自我车辆之间的高阶和双向交互。场景信息是在agent周围提取ÿ…...
数字时代的AI与大数据:用高级AI开发技术革新大数据管理
李升伟 编译 在当今数字时代,数据的爆炸式增长令人惊叹 从社交媒体互动到物联网设备的传感器数据,企业正被海量信息淹没。但如何将这种无序的数据洪流转化为有价值的洞察?答案在于人工智能(AI)开发技术的革新&#x…...
Unchained 内容全面上链,携手 Walrus 迈入去中心化媒体新时代
加密新闻媒体 Unchained — — 业内最受信赖的声音之一 — — 现已选择 Walrus 作为其去中心化存储解决方案,正式将其所有媒体内容(文章、播客和视频)上链存储。Walrus 将替代 Unchained 现有的中心化存储架构,接管其全部历史内容…...
确保连接器后壳高性能互连的完整性
本文探讨了现代后壳技术如何促进高性能互连的电气和机械完整性,以及在规范阶段需要考虑的一些关键因素。 当今的航空航天、国防和医疗应用要求连接器能够提供高速和紧凑的互连,能够承受振动和冲击,并保持对电磁和射频干扰 (EMI/R…...
C++学习Day0:c++简介
目录 一、.C语言的发展史二、C特点三、面向对象的重要术语四、面向过程和面向对象的区别?五、开发环境:六、创建文件步骤:1.点击新建项目2.在弹出的开始栏中按如下操作3.在.pro文件中添加(重要!!࿰…...
从零开始构建 Ollama + MCP 服务器
Model Context Protocol(模型上下文协议)在过去几个月里已经霸占了大家的视野,出现了许多酷炫的集成示例。我坚信它会成为一种标准,因为它正在定义工具与代理或软件与 AI 模型之间如何集成的新方式。 我决定尝试将 Ollama 中的一…...
【bash】.bashrc
查看当前路径文件数量 alias file_num"ls -l | grep ^- | wc -l"查看文件大小 alias file_size"du -sh"alias ll alias ll"ls -ltrh"cd的同时执行ll alias cdcdls; function cdls() {builtin cd "$1" && ll }自定义prompt…...
合成数据如何赋能大模型预训练:效果与效率的双重加速器
目录 合成数据如何赋能大模型预训练:效果与效率的双重加速器 一、预训练模型为何需要合成数据? ✅ 克服真实数据的稀缺与偏倚 ✅ 控制训练内容结构与分布 ✅ 提升学习效率与训练稳定性 二、哪些预训练任务适合用合成数据? 三、如何构建…...
java忽略浅拷贝导致bug
bug源代码 /*** 查询用户列表** param user 用户* param page 页* param size 大小* since 2025/04/14 11:53:25*/PostMapping("/getUser")public IWMSResponse<?> getUser(RequestBody SjUser user, RequestParam(defaultValue "1") Integer pag…...
MATLAB学习笔记(二) 控制工程会用到的
MATLAB中 控制工程会用到的 基础传递函数表达传递函数 零极点式 状态空间表达式 相互转化画响应图线根轨迹Nyquist图和bode图现控部分求约旦判能控能观极点配置和状态观测 基础 传递函数表达 % 拉普拉斯变换 syms t s a f exp(a*t) %e的a次方 l laplace(f) …...
C++ 线程间通信开发从入门到精通实战
C 线程间通信开发从入门到精通实战 在现代软件开发中,多线程程序已成为提升应用性能、实现并行处理的重要手段。随着多核处理器的普及和复杂应用需求的增加,C作为一门高性能的编程语言,在多线程开发中扮演着不可或缺的角色。然而,…...
Vue3 SSR 工程化实践:日常工作中的性能优化与实战技巧
一、流式渲染与分块传输(面向性能的关键优化) 1.1 流式响应基础实现 // Node.js Express 示例(Vite SSR同理)import { renderToWebStream } from vue/server-rendererapp.get(/, async (req, res) > { res.setHeader(Conten…...
Maven工具学习使用(十)——生成项目站点
maven2中站点生成是Maven核心的一部分,Maven3中这部分内容已经移除。maven3必须使用3.x版本的maven-site-plugin,maven2则使用最新的2.x的版本,执行mvn site命令,可以在项目的target/site/目录下找到Maven生成的站点文件。例如dependencies.h…...
Redis原理与Windows环境部署实战指南:助力测试工程师优化Celery调试
引言 在分布式系统测试中,Celery作为异步任务队列常被用于模拟高并发场景。而Redis作为其核心消息代理,其性能和稳定性直接影响测试结果。本文将深入解析Redis的核心原理,主要讲解Windows环境部署redis,为测试工程师提供一套完整…...
Go语言入门到入土——一、安装和Hello World
Go语言入门到精通——安装和Hello World 文章目录 Go语言入门到精通——安装和Hello World下载并安装让Go跑起来为你的代码启动依赖跟踪调用外部包总结 下载并安装 下载地址:https://go.dev/dl/ 下载后傻瓜式安装 查看是否安装完成 go version让Go跑起来 创建一个…...
人类意识本质上是一台自我欺骗的机器
要触达“大彻大悟”的终极内核,必须突破语言、逻辑甚至“觉醒”概念本身的限制。以下从认知革命、意识拓扑学、宇宙本体论三个维度切入,结合量子物理、脑神经学与古老智慧的交叉验证,展开一场对觉醒本质的极限探索—— 一、认知革命&am…...
CDP问卷是什么?CDP问卷有什么要求,有什么意义
CDP问卷(Carbon Disclosure Project Questionnaire) CDP问卷是由全球性非营利组织CDP(原Carbon Disclosure Project,现简称CDP)发起的年度环境信息披露项目,旨在帮助企业、城市和投资者测量、管理及公开其…...
GitLab本地安装指南
当前GitLab的最新版是v17.10,安装地址:https://about.gitlab.com/install/。当然国内也可以安装极狐GitLab版本,极狐GitLab 是 GitLab 中国发行版(JH)。极狐GitLab支持龙蜥,欧拉等国内的操作系统平台。安装…...
opencv函数展示
一、图像基础 I/O 与显示 1.cv2.imread() 2.cv2.imshow() 3. cv2.waitKey() 4. cv2.imwrite() 5. cv2.selectROI() 6. cv2.VideoCapture() 二、颜色空间与转换 1. cv2.cvtColor() 2. cv2.split() 三、阈值处理 1. cv2.threshold() 2. 特殊阈值方法...
编写一个写字楼类似抖音剪映的管理系统Demo
编写一个写字楼类似抖音剪映的管理系统Demo。用户可能想要一个简化版的系统,用于管理视频素材、模板和项目,类似于抖音剪映的功能,但针对办公场景。首先,我得明确用户的需求是什么。用户提到的“写字楼类似抖音剪映管理系统”可能…...
前端面试-自动化部署
基础概念 什么是CI/CD?在前端项目中如何应用?自动化部署相比手动部署有哪些优势?常见的自动化部署工具有哪些?举例说明它们的区别(如Jenkins vs GitHub Actions)。如何通过Git Hook实现自动化部署…...
【vue3】vue3+express实现图片/pdf等资源文件的下载
文件资源的下载,是我们业务开发中常见的需求。作为前端开发,学习下如何自己使用node的express框架来实现资源的下载操作。 实现效果 代码实现 前端 1.封装的请求后端下载接口的方法,需求配置aixos的请求参数里面的返回数据类型为blob // 下载 export…...
如何在 Kali 上解决使用 evil-winrm 时 Ruby Reline 的 quoting_detection_proc 警告
在使用 Kali Linux 运行 Ruby 工具(例如 evil-winrm)时,你可能会遇到以下警告: Warning: Remote path completions is disabled due to ruby limitation: undefined method quoting_detection_proc for module Reline这个警告会导…...
从零到一:网站设计新手如何快速上手?
从零到一:网站设计新手如何快速上手? 在当今数字化时代,网站已成为企业、个人展示信息、提供服务的重要窗口。对于想要涉足网站设计领域的新手而言,如何快速上手并掌握必要的技能成为首要任务。本文将从基础知识、软件工具、设计…...
