内存管理(三)
引用计数的存储在64位中引用计数可以直接存储在优化过的isa指针中也可能存储在SideTable类中。isa指针中存储的什么东西具体可以参考Runtime的本质(一)在isa里面有一个extra_rc参数其中rc就是retainCount引用计数的意思。引用计数器太大extra_rc中存储不下则has_sidetable_rc1引用计数器会存储在一个名为SideTable的类的属性中。structSideTable{spinlock_tslock;// 保证原子操作的自旋锁RefcountMap refcnts;//引用计数器存储地是一个哈希map表weak_table_tweak_table;//弱引用表也是哈希map存储}在源码中可以找到retainCount的源码-(NSUInteger)retainCount{return((id)self)-rootRetainCount();}点击进去sidetable_getExtraRC_nolock();的实现源码refcnts以对象的地址作为key引用计数作为value更多学习请看iOS管理对象内存的数据结构以及操做算法–SideTables、RefcountMap、weak_table_tweak指针相关知识点__strong会对对象产生强引用__weak会对对象产生弱引用并且在对象销毁的时候将指针置为nil__unsafe_unretain会对对象产生弱引用并且在对象销毁的时候什么也不做。如果再次使用该指针则会报野指针错误。weak指针的实现原理也就是weak指针指向对象销毁的时候weak指针怎么做到变为nil的。Runtime维护了一个weak表用于存储指向某个对象的所有weak指针。weak表其实是一个Hash哈希表Key是所指对象的地址Value是weak指针的地址这个地址的值是所指对象的地址数组。举个例子NSObject*strongObj[[NSObject alloc]init];__weak NSObject*obj1strongObj;__weak NSObject*obj2strongObj;Key指的是[[NSObject alloc] init],即NSObject 对象的地址Value指的是[obj1, obj2]obj1weak 变量当前保存的值也就是对象地址obj1weak 变量自身的存储地址obj2weak 变量当前保存的值obj2weak 变量自身的存储地址structweak_table_t{// 保存了所有指向指定对象的 weak 指针weak_entry_t*weak_entries;// 存储空间size_tnum_entries;// 参与判断引用计数辅助量uintptr_tmask;// hash key 最大偏移值uintptr_tmax_hash_displacement;};dealloc当一个对象要释放时会自动调用dealloc接下来调用的是dealloc_objc_rootDeallocrootDeallocobject_disposeobjc_destructInstance、free-(void)dealloc{_objc_rootDealloc(self);}void_objc_rootDealloc(id obj){assert(obj);obj-rootDealloc();}inlinevoidobjc_object::rootDealloc(){if(isTaggedPointer())return;// fixme necessary?//weakly_referenced是弱指针引用//强引用走yes弱引用走noif(fastpath(isa.nonpointer!isa.weakly_referenced!isa.has_assoc!isa.has_cxx_dtor!isa.has_sidetable_rc)){assert(!sidetable_present());free(this);}else{object_dispose((id)this);}}idobject_dispose(id obj){if(!obj)returnnil;objc_destructInstance(obj);free(obj);returnnil;}void*objc_destructInstance(id obj){if(obj){// Read all of the flags at once for performance.bool cxxobj-hasCxxDtor();bool assocobj-hasAssociatedObjects();// This order is important.if(cxx)object_cxxDestruct(obj);if(assoc)_object_remove_assocations(obj);obj-clearDeallocating();//将指向当前对象的弱指针置为nil}returnobj;}inlinevoidobjc_object::clearDeallocating(){if(slowpath(!isa.nonpointer)){// Slow path for raw pointer isa.sidetable_clearDeallocating();}elseif(slowpath(isa.weakly_referenced||isa.has_sidetable_rc)){// Slow path for non-pointer isa with weak refs and/or side table data.clearDeallocating_slow();}assert(!sidetable_present());}NEVER_INLINEvoidobjc_object::clearDeallocating_slow(){assert(isa.nonpointer(isa.weakly_referenced||isa.has_sidetable_rc));SideTabletableSideTables()[this];table.lock();if(isa.weakly_referenced){//如果是弱指针weak_clear_no_lock(table.weak_table,(id)this);}if(isa.has_sidetable_rc){table.refcnts.erase(this);}table.unlock();}一句话在64位下weak指针存储在一个weak_table哈希表中在dealloc中进行判断如果是weak指针则置为nil。更多关于weak学习weak 弱引用的实现方式iOS开发 - 底层解析weak的实现原理笔记-更深层次的了解iOS内存管理ARC都帮我们做了什么ARC主要是LLVM编译器RunTime系统相互协作的结果通过LLVM编译器帮我们自动做了retain、release、autorelease操作也就是自动做release操作是编译器特性。通过runtime监控系统在程序运行中将weak指针销毁置为nil在自动引用计数ARC的实现中LLVM编译器和Objective-C运行时RunTime系统共同协作实现了内存管理的自动化大大减少了开发者需要手动编写的retain/release代码量。以下是LLVM编译器和Objective-C运行时在ARC下各自的角色和作用LLVM编译器的角色自动插入内存管理代码在编译时LLVM编译器负责自动插入retain、release和autorelease调用。这是通过静态分析代码的方式完成的编译器会根据对象的生命周期以及所有权修饰符如__strong、__weak来决定何时以及在何处添加这些内存管理调用。优化内存管理操作LLVM编译器还会尝试优化这些自动插入的内存管理调用减少不必要的操作以提高性能。例如通过删除冗余的retain/release调用或者在编译期间合并多个可以安全合并的内存管理调用。强弱引用管理编译器负责处理强引用和弱引用的创建和销毁。对于弱引用编译器自动插入必要的代码来确保当所引用的对象被销毁时弱引用会被置为nil。Objective-C运行时RunTime的角色维护引用计数运行时负责在对象的生命周期内维护其引用计数。每次对象被retain时其计数增加每次release时计数减少。当计数达到零时对象被销毁。处理弱引用运行时维护一个弱引用表记录所有的弱引用。当一个对象被销毁时运行时会自动将所有指向该对象的弱引用置为nil防止悬垂指针的出现。自动释放池管理自动释放池Autorelease Pool的创建和销毁也是由运行时管理的。在ARC环境下虽然开发者仍然可以显式使用自动释放池但大多数autorelease调用是由编译器根据需要自动插入的。对象生命周期和内存管理事件的响应运行时会响应与对象生命周期和内存管理相关的事件如对象的构造、析构、引用计数变化等并执行相应的操作如调用析构函数来销毁对象。通过这种协作ARC能够实现对Objective-C对象内存管理的自动化大幅简化了内存管理工作同时减少了内存泄漏的风险。开发者不再需要手动调用retain/release可以更专注于业务逻辑的实现而内存管理相关的任务则交由编译器和运行时系统自动处理。autoreleasepool在之前的学习中我们有这样的说法autorelease是在autoreleasepool{}大括号结束的时候调用一下release操作。从运行结果来看确实是在17行结束的时候调用了person的dealloc方法也就是调用了release。本质是什么呢使用命令行xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m将main.m文件转换为main.cpp文件可以看到上图中的底层代码/* autoreleasepool */{__AtAutoreleasePool __autoreleasepool;NSLog((NSString*)__NSConstantStringImpl__var_folders_91_sht6gqgs1xj8b0_gczwc0ygc0000gn_T_main_64ff8e_mi_0);/* autoreleasepool */{__AtAutoreleasePool __autoreleasepool;YZPerson*person1((YZPerson*(*)(id,SEL))(void*)objc_msgSend)((id)((YZPerson*(*)(id,SEL))(void*)objc_msgSend)((id)((YZPerson*(*)(id,SEL))(void*)objc_msgSend)((id)objc_getClass(YZPerson),sel_registerName(alloc)),sel_registerName(init)),sel_registerName(autorelease));}NSLog((NSString*)__NSConstantStringImpl__var_folders_91_sht6gqgs1xj8b0_gczwc0ygc0000gn_T_main_64ff8e_mi_1);}简化下/* autoreleasepool */{__AtAutoreleasePool __autoreleasepool;NSLog(begin);/* autoreleasepool */{__AtAutoreleasePool __autoreleasepool;YZPerson*person1[[[YZPerson alloc]init]autorelease];}NSLog(end);}也就是autoreleasepool{YZPerson*person1[[[YZPerson alloc]init]autorelease];}转化为{__AtAutoreleasePool __autoreleasepool;YZPerson*person1[[[YZPerson alloc]init]autorelease];}在被转换的main.cpp文件中可以看到__AtAutoreleasePool的定义struct__AtAutoreleasePool{__AtAutoreleasePool(){//构造函数在创建结构体变量的时候调用atautoreleasepoolobjobjc_autoreleasePoolPush();}~__AtAutoreleasePool(){//析构函数在结构体变量销毁的时候调用objc_autoreleasePoolPop(atautoreleasepoolobj);}void*atautoreleasepoolobj;};也就是在执行__AtAutoreleasePool __autoreleasepool;代码的时候会调用构造函数atautoreleasepoolobj objc_autoreleasePoolPush();当{__AtAutoreleasePool __autoreleasepool;YZPerson*person1[[[YZPerson alloc]init]autorelease];}大括号结束的时候会调用析构函数objc_autoreleasePoolPop(atautoreleasepoolobj);整合上面也就是{__AtAutoreleasePool __autoreleasepool;atautoreleasepoolobjobjc_autoreleasePoolPush();YZPerson*person1[[[YZPerson alloc]init]autorelease];objc_autoreleasePoolPop(atautoreleasepoolobj);}autoreleasepool自动释放池的研究转化为对objc_autoreleasePoolPush()和objc_autoreleasePoolPop()的研究。objc_autoreleasePoolPush和objc_autoreleasePoolPop在objc源码中我们可以看到objc_autoreleasePoolPush和objc_autoreleasePoolPop的实现void*objc_autoreleasePoolPush(void){returnAutoreleasePoolPage::push();}voidobjc_autoreleasePoolPop(void*ctxt){AutoreleasePoolPage::pop(ctxt);}两个定义里面都用到了AutoreleasePoolPage自动释放池的主要底层数据结构是__AtAutoreleasePool、AutoreleasePoolPage。调用了autorelease的对象最终都是通过AutoreleasePoolPage对象来管理的。接下来我们来研究下AutoreleasePoolPage对象AutoreleasePoolPageAutoreleasePoolPage的定义简化后class AutoreleasePoolPage{magic_tconstmagic;id*next;pthread_tconstthread;//线程AutoreleasePoolPage*constparent;AutoreleasePoolPage*child;uint32_tconstdepth;uint32_thiwat;}从定义大致可以猜出自动释放池是与线程有关的。究竟是怎么回事我们继续往下看。每个AutoreleasePoolPage对象占用4096字节内存除了用来存放它内部的成员变量剩下的空间用来存放autorelease对象的地址(例子中的person地址值也被存放在了AutoreleasePoolPage内部也就是调用autorelease就会把地址存放在AutoreleasePoolPage内部)。所有AutoreleasePoolPage对象通过双向链表的形式链接在一起。调用push方法会将一个POOL_BOUNDARY入栈并且返回其存放的内存地址(POOL_BOUNDARY其实就是一个栈入口)调用pop方法时传入一个POOL_BOUNDARY的内存地址会从最后一个入栈的对象开始发送release消息直到遇到这个POOL_BOUNDARY。(找到栈的入口查找到最后入栈的对象。)id *next指向了下一个能存放autorelease对象地址的区域可以通过extern void _objc_autoreleasePoolPrint(void)私有函数来查看自动释放池的情况。0x2000-0x1000 0x1000D4096十进制4096双向链表通过parent、child指针来链接。一个对象调用了autorelease就会将该对象的地址值加入到page里面去autorelease与runloop使用autorelease修饰的对象在什么时候调用release方法?MRC环境下-(void)viewDidLoad{[super viewDidLoad];NSLog(1);autoreleasepool{YZPerson*person[[[YZPerson alloc]init]autorelease];}NSLog(3);}如果是将对象person加入到一个autoreleasepool中那么person对象会在自动释放池}时调用release方法。原因上面已经讲过。如果没有明显的加入自动释放池中又是什么时候调用release方法呢-(void)viewDidLoad{[super viewDidLoad];NSLog(1);YZPerson*person[[[YZPerson alloc]init]autorelease];NSLog(3);}结果13-[YZPerson dealloc]看着像是在viewDidLoad大括号结束的时候调用的release操作。真的是这样吗首先我们确定的是肯定不是在main.m文件中的autoreleasepool中去管理person的release操作。因为main.m文件中的autoreleasepool是整个程序生命周期延续的只有在程序结束的时候才会运行到}才会去销毁对象。如果person交给main.m文件中的autoreleasepool也就意味着person在整个程序运行过程中都不会被释放这明显是不对的。因此main.m文件中的autoreleasepool不负责person对象的release操作。从上面结果可以看出[YZPerson dealloc]是在[ViewController viewWillAppear:]和[ViewController viewDidAppear:]中间被执行的。也就是说并不是在48行代码执行的dealloc。直接打印[NSRunLoop mainRunLoop]我们在打印结果里面发现observers里面有这两个类型_wrapRunLoopWithAutoreleasePoolHandler拿取出来可以看到observers(CFRunLoopObserver 0x6000006e41e0 [0x7fff80617cb0]{valid Yes, activities 0x1, repeats Yes, order -2147483647, callout _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context CFArray 0x6000039bc690 [0x7fff80617cb0]{type mutable-small, count 1, values (\n\t0 : 0x7fc00e802038\n)}},CFRunLoopObserver 0x6000006e4280 [0x7fff80617cb0]{valid Yes, activities 0xa0, repeats Yes, order 2147483647, callout _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context CFArray 0x6000039bc690 [0x7fff80617cb0]{type mutable-small, count 1, values (\n\t0 : 0x7fc00e802038\n)}}其中第一个activities 0x1第二个activities 0xa0结合typedefCF_OPTIONS(CFOptionFlags,CFRunLoopActivity){kCFRunLoopEntry(1UL0),//1kCFRunLoopBeforeTimers(1UL1),//2kCFRunLoopBeforeSources(1UL2),//4kCFRunLoopBeforeWaiting(1UL5),//32kCFRunLoopAfterWaiting(1UL6),//64kCFRunLoopExit(1UL7),//128kCFRunLoopAllActivities0x0FFFFFFFU};可以看出第一个activities 0x1监听的是kCFRunLoopEntry第二个activities 0xa0换算成十进制是160也就是32128160监听的是kCFRunLoopBeforeWaiting | kCFRunLoopExit换句话说就是在runloop里面有两个跟AutoreleasePool相关的observer一个observer监听kCFRunLoopEntry一个observer监听kCFRunLoopBeforeWaiting | kCFRunLoopExit。iOS在主线程的runloop中注册了2个observer第一个observer监听了kCFRunLoopEntry会调用objc_autoreleasePoolPush()第二个observer监听了kCFRunLoopBeforeWaiting事件会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()第二个observer也监听了kCFRunLoopExit事件会调用objc_autoreleasePoolPop()翻译成白话也就是在线程中在runloop开始的时候就开始调用入栈操作将调用autorelease的对象存储在栈中。在runloop休眠前执行出栈release操作再执行一次入栈操作。在runloop销毁的时候执行出栈release操作。autorelease即自动释放即一段时间后释放也就是autoreleasepool{YZPerson*person1[[[YZPerson alloc]init]autorelease];//上述代码后person1的引用计数器为1为1的原因是由于alloc的作用//而autorelease没有对引用计数器再次加1变为2其作用是在适当时候做release操作将引用计数器变为0}也就是autorelease并没有增加对象的引用计数器数值仅仅是在消失的时候做release操作对引用计数器做减一操作而MRC下的retain或者ARC下的strong等操作只是改变了引用计数器大小而不会再次将对象加入自动释放池其他学习资料iOS - 聊聊 autorelease 和 autoreleasepool

相关新闻

机器人开始打工了,可量产才是生死线

机器人开始打工了,可量产才是生死线

WAIC场馆外的电闪雷鸣,仿佛地球生命创生一样,激发着具身智能这个新物种。 还没走进世博展览馆,周边路口上就能看见人形交通机器人在疏导车流,好像在预示:本届WAIC,具身智能绝对站C位。 从参展数量上即可证…

2026/7/24 23:05:05阅读更多 →
通用机器人迎来“珠峰级”考试:人类登顶满分,最强AI还在山脚

通用机器人迎来“珠峰级”考试:人类登顶满分,最强AI还在山脚

具身智能时代,通用机器人策略距离“登顶”还有多远?现有基准仍难以回答这个问题。 不少基准依赖简单、短程或技能范围狭窄的任务;仿真评估效率高、易扩展,但难以反映真实物理部署能力;真实世界评估更接近实际部署&…

2026/7/24 23:05:05阅读更多 →
嵌入式与Linux基础

嵌入式与Linux基础

嵌入式与Linux基础 一、什么是嵌入式? 简单来说是以计算机为基础,软硬件可裁剪的专用型计算机系统。 咱们平时用的电脑、笔记本,属于全能打工人:能打游戏、刷视频、写代码、追剧,啥活都能干,系统庞大&#…

2026/7/24 23:05:05阅读更多 →
大模型批量代码生成实战:从GLM-5.2重写应用到工程化落地

大模型批量代码生成实战:从GLM-5.2重写应用到工程化落地

这类标题很容易让人误解,以为某个 AI 模型真的“一夜之间”自动重写了操作系统里的所有应用。实际上,这通常指的是在特定比赛或演示场景下,利用大语言模型(如 GLM-5.2)的代码生成能力,对一组预设的、相对简单的应用程序示例或模板进行批量“重写”或“转换”。它解决的核…

2026/7/25 10:39:00阅读更多 →
Linux信号机制:原理、处理流程与编程实践

Linux信号机制:原理、处理流程与编程实践

1. Linux信号机制概述信号(Signal)是Linux系统中进程间通信(IPC)最古老的机制之一,最早可追溯到1970年代的Unix系统。当我们在终端按下CtrlC终止程序时,实际上就是通过SIGINT信号实现的进程间通信。信号本质…

2026/7/25 10:38:59阅读更多 →
大语言模型开发指南:从底层逻辑到实践技巧

大语言模型开发指南:从底层逻辑到实践技巧

1. 大语言模型入门:为什么开发者需要理解底层逻辑 第一次接触大语言模型(LLM)时,我被它强大的文本生成能力震撼了。但真正让我困惑的是:为什么同样的模型,有人能开发出惊艳的AI应用,而我的项目却…

2026/7/25 10:38:59阅读更多 →
CI/CD管道安全:从信任机制到行动验证的防护策略

CI/CD管道安全:从信任机制到行动验证的防护策略

在软件工程实践中,CI/CD 管道已经成为现代开发流程的核心基础设施。它负责代码的集成、测试、构建和部署,理论上应该是一个高度可信的自动化代理。然而,当这种自动化代理被赋予过高的信任权限,而缺乏有效的行动验证机制时&#xf…

2026/7/25 10:38:59阅读更多 →
嵌入式系统中断向量表与ECC内存保护:原理、初始化与故障注入测试

嵌入式系统中断向量表与ECC内存保护:原理、初始化与故障注入测试

1. 中断向量表与ECC:嵌入式系统可靠性的基石 在嵌入式系统,尤其是汽车电子和工业控制这类对实时性与可靠性要求严苛的领域,中断处理是系统响应的生命线。想象一下,一辆高速行驶的汽车,其防抱死制动系统(ABS…

2026/7/25 10:38:59阅读更多 →
从工具调用到任务规划:构建具备自主思考能力的AI Agent实战指南

从工具调用到任务规划:构建具备自主思考能力的AI Agent实战指南

最近几个月,AI Agent 这个词的热度几乎要溢出屏幕。无论是技术社区的热门话题,还是各大厂商发布的新产品,似乎都在宣告一个“AI Agent 时代”的到来。然而,一个有趣的现象是:当开发者们兴致勃勃地开始尝试时&#xff0…

2026/7/25 10:36:59阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/25 1:01:14阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/25 1:01:14阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/25 1:01:14阅读更多 →
突破文档下载限制:kill-doc让你看到的都能保存

突破文档下载限制:kill-doc让你看到的都能保存

突破文档下载限制:kill-doc让你看到的都能保存 【免费下载链接】kill-doc 看到经常有小伙伴们需要下载一些免费文档,但是相关网站浏览体验不好各种广告,各种登录验证,需要很多步骤才能下载文档,该脚本就是为了解决您的…

2026/7/25 0:01:16阅读更多 →
C++ string类模拟实现:从深拷贝到内存管理的完整指南

C++ string类模拟实现:从深拷贝到内存管理的完整指南

1. 项目概述:为什么我们要“手撕”string类?在C的学习道路上,尤其是从C语言过渡到C的“初阶”阶段,string类绝对是一个绕不开的核心。标准库里的std::string用起来太方便了,、find、substr,几个操作符和函数…

2026/7/25 0:01:16阅读更多 →
三角洲寻宝鼠工具:高效文件搜索与资源管理实战指南

三角洲寻宝鼠工具:高效文件搜索与资源管理实战指南

1. 先搞清楚“三角洲寻宝鼠”到底是什么工具从名称来看,“三角洲寻宝鼠”更像是一个资源查找或文件检索类工具,而不是游戏或娱乐软件。这类工具的核心价值在于帮助用户快速定位特定资源,比如文档、图片、压缩包或特定格式的文件。如果你经常需…

2026/7/25 0:01:16阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/24 23:01:03阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/24 19:00:40阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/24 19:00:40阅读更多 →