iOS内存管理
内存区域划分
内存区域 | 说明 |
---|---|
栈区 | 存放局部变量,系统自动分配和释放。 特点:容量小,速度快,有序 |
堆区 | 存放new或malloc操作的内存,比如对象。一般由程序员分配和释放,可能会出现内存泄露和循环引用的问题。 特点: 容量大,速度慢,无序 |
静态区 | 存放全局变量和静态变量。程序结束时,系统回收 |
常量区 | 存放常量。程序结束时,系统回收 |
代码区 | 存放二进制代码区域 |
从上述分类上看,我们在开发过程中主要涉及的是堆上内存的管理。
引用计数
Objective-C 和 Swift 的 iOS 运行时使用引用计数。使用引用计数的负面影响在于,如果开发人员不够小心,那么可能会出现重复的内存释放和循环引用的情况。
引用计数管理对象生命周期如下:
Objective-C
// 创建对象
BaseModel *model = [BaseModel new];
// 持有对象
id model1 = model;
Swift
// 创建对象
var model = BaseModel()
// 持有对象
var model1: Any = model
taggedPointer
从64位开始,iOS引入了Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储。
在没有使用Tagged Pointer之前, NSNumber等对象需要动态分配内存、维护引用计数等,NSNumber指针存储的是堆中NSNumber对象的地址值。
使用Tagged Pointer之后,NSNumber指针里面存储的数据变成了:Tag + Data(标记类型+数据),也就是将数据直接存储在了指针中,当指针(8字节)不够存储数据时,才会使用动态分配内存的方式来存储数据。
PS: Swift中经常使用的是值类型,比如Int,String,struct等。所以对指针的依赖比较少。
SideTables
SideTables由多个SideTable组成,SideTables本质是一个哈希表。SideTables的hash key为对象的地址。所以一个对象对应一个SideTable,一个SideTable又包含多个对象。
SideTable主要存放了对象的引用计数和弱引用相关信息。弱引用对象会先保存在SideTables。弱引用对象如果长期没有被清理掉的话会成为僵尸对象。这样可以僵尸对象长期占用内存情况下,不再需要保存僵尸对象,只保存引用计数和原对象指针内存占据非常小的SideTables。
SideTable中包含三个成员,自旋锁,引用计数表,弱引用表。
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
- slock是一个自旋锁,就是为了保证多线程访问安全性。如果当前锁已被其他线程获取,那么当前线程会不断的探测锁是否被释放,如果释放,自己第一时间获取这个锁
- refcnts本质是一个存储对象引用计数的hash表,key为对象,value为引用计数(优化过得isa中,引用计数主要存储在isa中)
- weak_table是存储对象弱引用的一个结构体
SideTables结构图如下:
ARC 和 MRC
MRC是需要调用对应的方法来管理引用计数。
ARC是自动管理引用计数。
PS:
(1)Objective-C是支持MRC和ARC,Swift是不支持MRC,只支持ARC的。
(2)ARC是通过LLVM和Runtime协作的,ARC禁止手动调用
retain/release
(3)Objective-C只是 Cocoa API中支持了ARC。Objective-C 中的其他常用 API,例如 Core Graphics,不支持 ARC。
MRC | ARC | |
---|---|---|
strong | 无 | ARC特有,MRC没有,相当于MRC模式的retain |
retain | 手动创建 | 自动创建 |
assgin | 可以用来修饰对象类型,也可以用来修饰基本数据类型。修饰对象类型的时候,对象的引用计数不会随着引用次数的增加而增加,也就是说被释放之前,引用计数永远是1 | 只能用来修饰基本数据类型,不能用来修饰对象类型。除此之外,还用来修饰代理对象。 |
weak | 无 | ARC特有,MRC没有,相当于MRC模式的assgin |