持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情
引用计数 & 引用计数表
- OC对象创建完之后引用计数默认为1
- 在其他对象中需要持有这个对象时,调用retain,引用计数加1。
- 需要释放一个对象时调用release,就将该对象的引用计数减1。
- 当引用计数减为0时该对象就会销毁。
任何对象都可能被一个或者多个指针引用。只要一个对象至少被一个指针引用,它就会继续存在。如果一个对象没有被指针引用,runtime会自动销毁它。
Cocoa 设置了以下策略:
-
当调用名称以“alloc”、“new”、“copy”或“mutableCopy”开头的方法创建对象(例如:
alloc或newObject)mutableCopy -
通过向对象发送
release消息或autorelease消息来放释放对象
ARC做了什么
ARC利用LLVM+Runtime自动补全了必要的一些release和autorelease的调用。比如大括号作用域中的临时变量自动添加了释放的代码。另外runtime又提供了weak相关的操作来确保内存释放后善后工作有保障,避免坏内存被引用,引发运行时异常。
Autorelease
自动释放池:AutoreleasePool
- AutoreleasePool是由若干个AutoreleasePoolPage以双向链表的形式组合而成。
- AutoreleasePoolPage类型为struct AutoreleasePoolPageData
- AutoreleasePoolPage每个对象会开辟4096字节内存(虚拟内存一页的大小)
- 除了结构体本身实例变量(结构体的大小)所占空间,剩下的空间全部用来储存autorelease对象的地址
- AutoreleasePoolPage的第一页会包含哨兵对象,哨兵对象占位8字节,每个新加入的对象为8字节,所以第一页最多可以存储504个对象,从第二页开始最多可以存储505个对象。
- AutoreleasepoolPage 通过压栈的方式来存储每个autorelease的对象(从低地址到高地址)。
- 其中next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置,当 next指针指向begin时,表示 AutoreleasePoolPage 为空;
- 当 next指针指向end时,表示 AutoreleasePoolPage 已满
- 新建一个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,并做出更加迅速的操作。