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

相关推荐
黑科技iOS上架2 小时前
ios应用被封号后再次上架很难么?
经验分享·ios
柚鸥ASO优化7 小时前
一篇讲透安卓ASO!开发者千万别只盯着iOS了
android·ios·aso优化
黑科技iOS上架7 小时前
Swift Package Manager包管理工具的优缺点
经验分享·ios
大熊猫侯佩11 小时前
Swift 6.4 的 Ref / MutableRef 大揭秘:给值类型开一扇“安全的小窗”
ios·swift·编程语言
黑科技iOS上架12 小时前
没有mac电脑如何借助windows系统上传ipa到App Store
经验分享·ios
音视频牛哥12 小时前
iOS如何实现RTSP/RTMP低延迟播放?SmartMediaKit播放器集成说明
objective-c·低延迟rtsp播放器·低延迟rtmp播放器·ios rtmp player·ios rtsp player·ios平台rtsp播放器·ios平台rtmp播放器
Layer13 小时前
从 WWDC 26 空间重构(Spatial Reframing)再看端侧 2D 转 3D 的技术演进
ios·aigc
Cutecat_1 天前
视频字幕处理工具横向:提取模式 vs 编辑模式,该如何选择
android·前端·ios·语音识别
大熊猫侯佩1 天前
WWDC26 SwiftUI 进化之路:砸碎黑盒,彻底迎来开发自由!
ios·swiftui·swift