OC底层那些事儿:一文搞懂内存管理

195 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

引用计数 & 引用计数表

  • OC对象创建完之后引用计数默认为1
  • 在其他对象中需要持有这个对象时,调用retain,引用计数加1。
  • 需要释放一个对象时调用release,就将该对象的引用计数减1。
  • 当引用计数减为0时该对象就会销毁。

任何对象都可能被一个或者多个指针引用。只要一个对象至少被一个指针引用,它就会继续存在。如果一个对象没有被指针引用,runtime会自动销毁它。

Cocoa 设置了以下策略:

  • 当调用名称以“alloc”、“new”、“copy”或“mutableCopy”开头的方法创建对象(例如:allocnewObjectmutableCopy

  • 通过向对象发送release消息或autorelease消息来放释放对象

ARC做了什么

ARC利用LLVM+Runtime自动补全了必要的一些release和autorelease的调用。比如大括号作用域中的临时变量自动添加了释放的代码。另外runtime又提供了weak相关的操作来确保内存释放后善后工作有保障,避免坏内存被引用,引发运行时异常。

Autorelease

自动释放池:AutoreleasePool

  1. AutoreleasePool是由若干个AutoreleasePoolPage以双向链表的形式组合而成。
  2. AutoreleasePoolPage类型为struct AutoreleasePoolPageData
  3. AutoreleasePoolPage每个对象会开辟4096字节内存(虚拟内存一页的大小)
  4. 除了结构体本身实例变量(结构体的大小)所占空间,剩下的空间全部用来储存autorelease对象的地址
  5. AutoreleasePoolPage的第一页会包含哨兵对象,哨兵对象占位8字节,每个新加入的对象为8字节,所以第一页最多可以存储504个对象,从第二页开始最多可以存储505个对象。
  6. AutoreleasepoolPage 通过压栈的方式来存储每个autorelease的对象(从低地址到高地址)。
  7. 其中next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置,当 next指针指向begin时,表示 AutoreleasePoolPage 为空;
  8. 当 next指针指向end时,表示 AutoreleasePoolPage 已满
  9. 新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的AutoreleasePoolPage插入,同样的新AutoreleasePoolPage的next指针被初始化在栈底(指向begin的位置)

AutoreleasePoolPage和runloop

在一次runloop通知observer即将休眠或者即将退出的时候会释放main函数中最外层的自动释放池中需要释放的指针。

查看自动释放池状态

可以通过私有函数 extern void _objc_autoreleasePoolPrint(void)来打印自动释放池的当前状态。

dealloc过程做了哪些事

weak

weak指针存储位置

在64位机中,引用计数可以直接存储在优化过的isa指针中,也可能存储在SideTable结构体中中

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;
}

isa指针的文章中我提到过,isa指针的位域结构体中有个weakly_referenced 字段来表示改对象是否被弱引用过。如果有引用过在dealloc的底层实现中清空内存的时候会去操作SideTable中的weak_table

存储和清理weak_table大致操作描述

weak_table 使用 objc_storeWeak(&weakp, obj)函数 存储弱引用指针

objc_storeWeak函数把第二个参数--赋值对象obj的内存地址作为键值key,将第一个参数--weak修饰的指针weakp的内存地址(&weakp)作为value,注册到 weak 表中。

第二个参数obj释放之后,那么把变量weakp的内存地址&weakp从weak表中删除。

当被引用的对象地址释放后

weak和assign的不同

__weak__unsafe_unretained都不对产生强引用,即不会引起引用计数的变化,但当他们指向的对象销毁后 weak指针会自动被设置为nil,而__unsafe_unretained 修饰的指针还会指向原来的地址,这就会产生坏内存访问的错误,即野指针

64位机Apple的优化:TaggedPointer

  • 用于优化小对象NSNumber、NSString、NSDate 的存储和读取。
  • iOS平台指针的最低有效位/Mac平台最高有效位为1则为TaggedPointer
  • 在没有TaggedPointer之前,小对象也需要动态分配内存,维护引用计数等操作,这就给系统带来了很多不必要的开销。
  • 使用了TaggedPointer后小对象的指针中存储数据变成了Tag+Data,即直接将数据存储在了指针中。
  • objc_msgSend、dealloc方法内部实现中都会识别Tagged Pointer,并做出更加迅速的操作。