iOS 客户端- iOS内存管理 | 青训营笔记

59 阅读6分钟

这是我参与「第四届青训营 」笔记创作活动的的第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 掉,引起闪退

引用参考:

课外补充引用:

文章学习来源:

感谢以上作者的文章,今天的学习收获满满!!Thanks and HappyCoding!