这是我参与「第四届青训营 」笔记创作活动的的第19天
前言
*今天学习到的是【iOS 客户端专场 学习资料一】第四届字节跳动青训营的第四节:iOS系统框架之# iOS内存管理;ios内存管理系统提供的有不同的内存管理方案,大致有如下三种:
- TaggedPointer (对于一些小对象,比如说NSNumber,NSString等采用此种方案)
- NONPOINTER_ISA (64位架构下iOS应用程序)
- 散列表 (散列表为复杂的数据结构,包含了引用计数表和弱引用表) *
iOS内存管理
Objective-C内存管理:
-
术语
-
持有 = retain = 引用计数 +1
-
释放 = release = 引用计数 -1
-
MRC Manual Reference Counting
-
-
在OC中,使用引用计数来进行内存管理。每个对象都有一个与其相对应的引用计数器,当持有一个对象,这个对象的引用计数就会递增;当这个对象的某个持有被释放,这个对象的引用计数就会递减。当这个对象的引用计数变为0,那么这个对象就会被系统回收。
-
“alloc”, “new”, “copy”, or “mutableCopy” 开头的方法创建的对象,引用计数 = 1
-
// 使用了alloc分配了内存,obj指向了对象,该对象本身引用计数为1,不需要retain id obj = [[NSObject alloc] init]; // 使用了new分配了内存,objc指向了对象,该对象本身引用计数为1,不需要retain id obj = [NSObject new];
-
-
对一个对象发送 retain 消息,可以让对象的引用计数 +1,
-
对一个对象发送 release 消息,可以让对象的引用计数 -1。
-
当对象的引用计数为 0 时,即将销毁的时候,系统会向对象发送一条dealloc消息 【不能直接调用 dealloc】。
-
//NSMutableArray通过类方法array产生了对象(并没有使用alloc、new、copy、mutableCopt来产生对象),因此该对象不属于obj自身产生的
// 因此,需要使用retain方法让对象计数器+1,从而obj可以持有该对象(尽管该对象不是他产生的)
id obj1 = [NSMutableArray array];
[obj1 retain];
//NSMutableArray通过类方法array产生了对象(并没有使用alloc、new、copy、mutableCopt来产生对象),因此该对象不属于obj自身产生的
// 因此,需要使用retain方法让对象计数器+1,从而obj可以持有该对象(尽管该对象不是他产生的)
id obj1 = [NSMutableArray array];
[obj1 retain];
// 释放一个不属于自己的对象
id obj = [NSMutableArray array];
//obj没有进行retain操作而进行release操作,然后autoreleasePool也会对其进行一次release操作,导致奔溃。
[obj release];
Autorelease-Pool
- 对象会放到自动释放池,统一释放
-
autorelease和release的区别是:
- release是马上释放对某个对象的强引用;
- autorelease是延迟释放某个对象。
- 在部分场景下,使用Autorelease pool可以降低内存峰值
ARC ****Automatic Reference Counting 【这里是课程重点】
-
retain/release都不用考虑
- 只需要初始化的时候 [NSObject alloc] init]
- 可以自定义 dealloc
-
只要有一个强指针在内存指向对象,对象就不能释放
- ARC 销毁时机是强引用的个数 = 0
- 而不是引用计数 = 0
-
默认所有对象变量的指针都是强指针
- 弱指针需显式声明
-
__weak Person *p2 = [Person new]; 复制代码
课外补充:
物理内存 一个设备的 RAM 大小。以下是维基百科上的资料:
简单来说,iPhone 8(不包括 plus) 和 iPhone 7(不包括 plus)及之前都是 2G 内存,iPhone 6 和 6 plus 及之前都是 1G 内存。
虚拟内存(VM for Virtual Memory) 每个进程都有一个自己私有的虚拟内存空间。对于32位设备来说是 4GB,而64位设备(5s以后的设备)是 18EB(1EB = 1000PB, 1PB = 1000TB),映射到物理内存空间。
页 内存管理、映射中的基本单位是页,一页的大小是 4kb(早期设备)或者 16kb(A7 芯片及以后)
因为有页的存在,每次申请内存都必须以页为单位。然而这样一来,如果只是申请几个 byte,却不得不分配一页(16kb),是非常大的浪费。因此在用户态我们有 “heap” 的概念。
Page In/Out 由于虚拟内存的空间远远大于物理内存,在任意一个时间点,虚拟内存中的一个页并不一定总是在物理内存中,而是可能被暂时存到了磁盘上,这样物理内存便可以暂时释放这部分空间,供优先级更高的任务使用,因此磁盘可以作为 backing store 以扩展物理内存(MacOS 中有,iOS 没有)。另一种可能是加载一个比较大的文件/动态库,每次使用我们可能只需要加载其中的一部分,那么就可以使用 mmap 映射这个文件到虚拟内存空间,这样当我们访问其中一部分时,系统会自动把这一部分从磁盘加载到内存,而不加载其余部分。
这样把磁盘中的数据写到内存/从内存中写回磁盘成为 page in/out。
Wired memory 无法被 page out 的内存,主要为系统层所用,开发者不需要考虑这些。
VM Region 一个 VM Region 是指一段连续的内存页(在虚拟地址空间里),这些页拥有相同的属性(如读写权限、是否是 wired,也就是是否能被 page out)。举几个例子:
mapped file,即映射到磁盘的一个文件
__TEXT,r-x,多数为二进制
__DATA,rw-,为可读写数据
MALLOC_(SIZE),顾名思义是 malloc 申请的内存
VM Object 每个 VM Region 对应一个数据结构,名为 VM Object。Object 会记录这个 Region 内存的属性
Resident Page 当前正在物理内存中的页(没有被交换出去)
与其他App共存的情况 app 内存消耗较低,同时其他 app 也非常“自律”,不会大手大脚地消耗内存,那么即使切换到其他应用,我们自己的 app 依然是“活着”的,保留了用户的使用状态,体验较好
app 内存消耗较低,但是其他 app 非常消耗内存(可能是使用不当,也可能是本身就非常消耗内存,比如大型游戏),那除了当前在前台的进程,其他 app 都会被系统回收,用来给活跃进程提供内存资源。这种情况我们无法控制
app 内存消耗比较大,那切换到其他 app 以后,即使其他 app 向系统申请不是特别大的内存,系统也会因为资源紧张,优先把消耗内存较多的 app 回收掉。用户会发现只要 app 一旦退到后台,过会再打开时就会重新加载
app 内存消耗特别大,在前台运行时就有可能被系统 kill 掉,引起闪退
引用参考:
课外补充引用:
文章学习来源:
- iOS 客户端专场 学习资料二】第四届字节跳动青训营(第四节: iOS内存管理 )
感谢以上作者的文章,今天的学习收获满满!!Thanks and HappyCoding!