ios内存管理

内存管理方案

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实现 对象是否可以释放会进行判断:共五个条件

  1. 是否是非指针型的isa

  2. 是否存在弱引用指针

  3. 是否存在关联对象

  4. 是否涉及到C++相关内容

  5. 是否通过引用计数表来维护

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)
  1. 查找 referent 对应的 weak_entry_t(通过weak_entry_for_referent:函数内部通过哈希算法计算对应index,从而在弱引用表中得到这个对象对应的弱引用的数组)

  2. 若找到 entry:调用append_referrer将新的弱引用指针地址添加到 entry 中

  3. 若未找到 entry:创建新的 weak_entry_t,初始化 referent 和第一个 referrer,检查 weak_table 是否需要扩容(weak_grow_maybe),将新 entry 插入 weak_table(weak_entry_insert)

  4. 标记对象为 "被弱引用"(在 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逻辑:

  1. 以对象为 key,从 weak_table 中查找对应的 weak_entry_t

  2. 遍历 entry 中的所有 referrers(弱引用指针地址)

  3. 对每个 referrer 执行*referrer = nil,将弱引用指针自动置空

  4. 将该 entry 从 weak_table 中删除,清理所有相关记录

  5. 清除对象的 "被弱引用" 标记

自动释放池

自动释放池结构

  1. 以栈为结点通过双向链表的形式组合

  2. 是与线程一一对应的

    #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逻辑
  1. 获取当前线程对应的 Page 链表(线程隔离)。

  2. 找到链表最后一个可用 Page。

  3. 将 POOL_BOUNDARY 哨兵 存入 Page 的 next 位置,next 后移。

  4. 返回哨兵的内存地址(称为 token),用于后续销毁池时定位边界。

autorelease逻辑
  1. 校验:nil、TaggedPointer 对象直接跳过,不加入池。

  2. 获取当前线程的 Page 链表。

  3. 检查当前 Page 是否还有空闲位置:

  4. 有空闲:把对象指针存入 *next,next++。无空闲:新建 child Page,再存入对象。

pop逻辑
  1. 根据传入的 token(哨兵地址),定位到当前池的边界。

  2. 从当前 Page 的 next 位置向前遍历:每取出一个对象指针,就调用 obj release(引用计数 -1)。

  3. 持续遍历,直到遇到 POOL_BOUNDARY 哨兵为止。

  4. 遍历完成后,将 next 指针重置到哨兵位置,当前池销毁。 关键:只会释放「当前哨兵之后」的对象,不会影响外层池。

note:在每次runloop快要结束的时候调用pop来进行对象释放

循环引用

自循环引用

对象强持有obj变量,同时变量指向该对象

相互循环引用

A中的obj指向B,B中的obj指向A

多循环引用

如何破除循环引用

  1. 避免产生循环引用

  2. 在合适的位置手动断环

具体的解决方案

  1. __weak

  2. __block

  3. __unsafe_unretained 修饰对象不会增加引用计数,避免循环引用,如果在某一时机被释放,会产生悬垂指针,一般不建议使用

循环引用常见场景

场景 1:Delegate / DataSource 代理(UI 组件高频)

问题成因 视图 / 控件(UIView、UITableView、UICollectionView 等)的 delegate / dataSource 通常指向控制器 ViewController:

  1. VC 强持有 View(self.view)

  2. 若 View 的 delegate 用 strong 修饰 → View 又强持有 VC

  3. 形成闭环:VC(strong) → View(strong) → VC

解决方法:所有 UI 组件的代理、数据源,统一使用 weak 修饰

场景2: Block 循环引用

暂时省略,会后续在Block文章中补充

  1. self(VC/View)强持有 Timer;

  2. Timer 创建时,会底层强持有 target(也就是 self);

  3. 闭环:self(strong) → Timer(strong) → self。

即使对象对NSTimer弱引用持有,由于RunLoop强持有对象,所有当VC销毁,对象也不会释放

解决方案:

对于非重复定时器

  1. 在定时器回调方法当中手动调用invalidate方法

如果是对于重复多次回调的计时器

  1. 添加中间对象

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

相关推荐
人月神话Lee1 天前
我做了个不要账号、不要定位权限的旅行 App,聊聊那些「不做」的决定
ios·ai编程·产品
2501_916007472 天前
Bundle ID 注册与管理 App ID 创建指南 命名规则 权限开关与删除注意事项
android·ios·小程序·https·uni-app·iphone·webview
大熊猫侯佩2 天前
WWDC26:把 SwiftUI Toolbar 真正握在手心里
swiftui·swift·apple
大熊猫侯佩2 天前
WWDC 26 全新 ResultsObserver:终于能在 View 外面盯着 SwiftData 了
swiftui·swift·apple
大熊猫侯佩2 天前
别再把大模型供在云端了,WWDC26 CoreAI 大模型下凡实战
ai编程·swift·wwdc
2501_915909062 天前
iOS 证书创建与管理 类型限制 p12 密码与云备份实操
android·ios·小程序·https·uni-app·iphone·webview
00后程序员张2 天前
有没有更轻量的 iOS 开发环境?快蝎kxapp轻量化路径与构成
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
ii_best2 天前
iOS 投屏ios群控鹰眼中控系统安装指南|电脑端 + TestFlight + 开发者模式实操
ios·自动化·按键精灵
sakiko_3 天前
Swift学习笔记39-实战注意事项
前端·笔记·学习·swift