内存管理方案
nonpointer-isa
末位indexed用来表示当前指针是否是纯的isa指针还是包含一些内存管理信息
1位has_assoc用来表示是否存在关联对象
2位has_cxx_dtor当前对象是否使用到C++的一些代码
3 - 35 共33位shiftcls表示当前对象类对象的指针地址
36 - 41共6位magic
第42位weakly_referenced表示当前对象是否存在弱引用指针
第43位deallocating是否正在进行内存释放操作
第44位has_sidetable_rc表示如果当前isa指针对应引用计数超过表示范围,需要使用外挂sidetable
45-63位extra_rc用来存储引用计数
散列表方式

sideTables()作为一个哈希表,通过对象指针可以找到其对应的引用计数表

sideTable的结构包含下面三个内容:自旋锁,引用计数表,弱引用表
自旋锁
忙等的锁:获取不到会阻塞当前线程,等待锁释放的信号进行唤醒
引用计数表:Map,通过指针查找引用计数
弱引用表:哈希表,通过对象指针找到对应的结构体数组,结构体数组里面存储的每个对象实际上就是弱引用指针
引用计数管理
retain实现

release实现

retainCount实现

dealloc实现 对象是否可以释放会进行判断:共五个条件
-
是否是非指针型的isa
-
是否存在弱引用指针
-
是否存在关联对象
-
是否涉及到C++相关内容
-
是否通过引用计数表来维护

object_dispose()实现:这里会先调用objc_destructinstance这个函数,然后再free

clearDeallocating实现

弱引用管理
弱引用初始化
当声明__weak id obj = object;时,编译器会生成以下调用链:
objc_initWeak(&obj, object) → storeWeak<DontHaveOld, DoHaveNew>(&obj, object)
在storeWeak中会调用weak_register_no_lock这个函数来添加弱引用
弱引用添加
id weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, WeakRegisterDeallocatingOptions options)
-
查找 referent 对应的 weak_entry_t(通过weak_entry_for_referent:函数内部通过哈希算法计算对应index,从而在弱引用表中得到这个对象对应的弱引用的数组)
-
若找到 entry:调用append_referrer将新的弱引用指针地址添加到 entry 中
-
若未找到 entry:创建新的 weak_entry_t,初始化 referent 和第一个 referrer,检查 weak_table 是否需要扩容(weak_grow_maybe),将新 entry 插入 weak_table(weak_entry_insert)
-
标记对象为 "被弱引用"(在 refcnts 中设置SIDE_TABLE_WEAKLY_REFERENCED位)
这里描述一下weak_entry_t的结构
struct weak_entry_t {
DisguisedPtr<objc_object> referent; // 被弱引用的对象(指针伪装处理)
union {
struct {
weak_referrer_t *referrers; // 动态数组,存储弱引用指针地址
uintptr_t num_refs; // 弱引用计数
uintptr_t mask; // 动态数组掩码
uintptr_t max_hash_displacement;
};
struct {
// 内联数组,当弱引用数≤4时使用,避免动态内存分配
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; // WEAK_INLINE_COUNT=4
};
};
};
清楚weak变量,将其设置为nil
dealloc → clearDeallocating → sidetable_clearDeallocating → weak_clear_no_lock
关键函数weak_clear_no_lock逻辑:
-
以对象为 key,从 weak_table 中查找对应的 weak_entry_t
-
遍历 entry 中的所有 referrers(弱引用指针地址)
-
对每个 referrer 执行*referrer = nil,将弱引用指针自动置空
-
将该 entry 从 weak_table 中删除,清理所有相关记录
-
清除对象的 "被弱引用" 标记
自动释放池
自动释放池结构
-
以栈为结点通过双向链表的形式组合
-
是与线程一一对应的
#define PAGE_SIZE 4096 // 单页总大小:4KB
struct AutoreleasePoolPage {
// 校验字段:检测内存越界、结构损坏,调试用
uint64_t magic;
// 当前 Page 所属线程(线程隔离核心)
pthread_t thread;
// 双向链表指针:前驱页、后继页
AutoreleasePoolPage *parent;
AutoreleasePoolPage *child;
// 指向 Page 中下一个空闲位置(核心指针)
id *next;// 预留空间 + 动态存储区:存放 autorelease 的对象指针 id objects[]; // 哨兵对象(特殊标记,不是真实 OC 对象) static const uintptr_t POOL_BOUNDARY;};
完整工作流程
池创建 (Push) → 对象入池 (autorelease) → 池销毁 (Pop/Drain)
push逻辑
-
获取当前线程对应的 Page 链表(线程隔离)。
-
找到链表最后一个可用 Page。
-
将 POOL_BOUNDARY 哨兵 存入 Page 的 next 位置,next 后移。
-
返回哨兵的内存地址(称为 token),用于后续销毁池时定位边界。
autorelease逻辑
-
校验:nil、TaggedPointer 对象直接跳过,不加入池。
-
获取当前线程的 Page 链表。
-
检查当前 Page 是否还有空闲位置:
-
有空闲:把对象指针存入 *next,next++。无空闲:新建 child Page,再存入对象。
pop逻辑
-
根据传入的 token(哨兵地址),定位到当前池的边界。
-
从当前 Page 的 next 位置向前遍历:每取出一个对象指针,就调用 obj release(引用计数 -1)。
-
持续遍历,直到遇到 POOL_BOUNDARY 哨兵为止。
-
遍历完成后,将 next 指针重置到哨兵位置,当前池销毁。 关键:只会释放「当前哨兵之后」的对象,不会影响外层池。
note:在每次runloop快要结束的时候调用pop来进行对象释放
循环引用
自循环引用

对象强持有obj变量,同时变量指向该对象
相互循环引用

A中的obj指向B,B中的obj指向A
多循环引用

如何破除循环引用
-
避免产生循环引用
-
在合适的位置手动断环
具体的解决方案
-
__weak

-
__block

-
__unsafe_unretained 修饰对象不会增加引用计数,避免循环引用,如果在某一时机被释放,会产生悬垂指针,一般不建议使用
循环引用常见场景
场景 1:Delegate / DataSource 代理(UI 组件高频)
问题成因 视图 / 控件(UIView、UITableView、UICollectionView 等)的 delegate / dataSource 通常指向控制器 ViewController:
-
VC 强持有 View(self.view)
-
若 View 的 delegate 用 strong 修饰 → View 又强持有 VC
-
形成闭环:VC(strong) → View(strong) → VC
解决方法:所有 UI 组件的代理、数据源,统一使用 weak 修饰
场景2: Block 循环引用
暂时省略,会后续在Block文章中补充
场景3:NSTimer / CADisplayLink 定时器
-
self(VC/View)强持有 Timer;
-
Timer 创建时,会底层强持有 target(也就是 self);
-
闭环:self(strong) → Timer(strong) → self。

即使对象对NSTimer弱引用持有,由于RunLoop强持有对象,所有当VC销毁,对象也不会释放
解决方案:
对于非重复定时器
- 在定时器回调方法当中手动调用invalidate方法
如果是对于重复多次回调的计时器
-
添加中间对象

如果中间对象持有的指向对象的弱引用指针被置为nil,在对应的回调方法中将NStimer给无效,并置为nil