iOS内存管理

一、iOS 内存管理总体模型

1️⃣ 物理内存 vs 虚拟内存

• 物理内存(RAM):真实存在的内存

• 虚拟内存:

• 每个进程看到的是连续地址空间

• iOS 使用 VM + 页(page)机制

• 页大小:16KB(arm64)

iOS 特点

• ❌ 没有 swap 到磁盘

• ✅ 内存压力大 → 直接杀进程

• 优先级:

1. 前台 App

2. 后台音频 / 导航

3. 后台普通 App(直接 kill)

👉 结论:

iOS 内存泄漏 ≠ 变慢,而是 闪退 / 被系统杀

二、ARC(Automatic Reference Counting)

1️⃣ ARC 本质

ARC 不是 GC,是 编译期插入 retain / release

[obj retain];

[obj release];

由编译器在合适位置自动插入。

2️⃣ 引用计数规则(核心)

3️⃣ dealloc 调用时机

• 引用计数 == 0

• 不会立即释放内存,而是:

• 调用 dealloc

• 标记可回收

• 由 RunLoop 合适时机回收

⚠️ 面试点

dealloc 不一定立即 free 内存

三、属性修饰符与内存

1️⃣ strong / weak / assign / copy

strong

@property (nonatomic, strong) NSObject *obj;

• 持有对象

• 参与引用计数

• 默认 ARC 下对象属性

weak

@property (nonatomic, weak) NSObject *obj;

• 不增加引用计数

• 对象释放 → 自动置 nil(runtime 支持)

• 解决循环引用

⚠️ weak 底层:

• runtime 维护 weak table

• dealloc 时清空所有 weak 指针

assign

• 不管理生命周期

• 常用于:

• 基本数据类型

• 非 ARC / unsafe_unretained

🚨 危险

@property (nonatomic, assign) NSObject *obj; // 野指针风险

copy(非常高频)

@property (nonatomic, copy) NSString *name;

作用:

• 防止可变对象被外部修改

NSMutableString *str = ...

self.name = str;

[str appendString:@"xxx"]; // 不影响 name

• NSString / block / NSArray / NSDictionary 推荐 copy

2️⃣ block 的内存管理(重点)

block 类型

ARC 下

• block 赋值给 strong / copy → 自动拷贝到堆

self.block = ^{

NSLog(@"%@", self);

};

⚠️ 循环引用

self.block = ^{

self.name = @"abc";

};

解决:

__weak typeof(self) weakSelf = self;

self.block = ^{

weakSelf.name = @"abc";

};

四、自动释放池(AutoreleasePool)

1️⃣ autorelease 是什么?

NSString *str = [NSString stringWithFormat:@"abc"];

• 对象加入 当前 autorelease pool

• pool drain 时统一 release

2️⃣ RunLoop 与 AutoreleasePool

系统自动创建 pool:

• 每次 RunLoop 开始

• 每次 RunLoop 休眠前

kCFRunLoopEntry

→ 创建 pool

kCFRunLoopBeforeWaiting

→ drain pool

3️⃣ 为什么 for 循环要手动加 pool?

for (...) {

@autoreleasepool {

// 大量临时对象

}

}

否则:

• 所有临时对象堆积到一次 RunLoop

• 瞬间内存暴涨

📌 音视频 / 图片处理 必考 + 必踩坑

五、循环引用(内存泄漏核心来源)

1️⃣ 常见循环引用场景

对象 ↔ block

self.block = ^{

self.doSomething;

};

delegate

@property (nonatomic, strong) id delegate;

应为:

@property (nonatomic, weak) id delegate;

2️⃣ NSTimer 循环引用(高频)

self.timer = [NSTimer scheduledTimerWithTimeInterval:1

target:self

selector:@selector(tick)

userInfo:nil

repeats:YES];

形成:

self → timer → self

解决方案:

• block + weak

• 中间 proxy(NSProxy)

3️⃣ CADisplayLink

和 NSTimer 一样的问题,更隐蔽

六、内存分区

七、内存警告(Memory Warning)

1️⃣ 系统何时发?

• 可用内存低

• VM 压力过大

2️⃣ 响应方式

UIViewController

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

}

应该做什么?

• 释放:

• 缓存

• 图片

• 可重建数据

❌ 不要释放:

• 正在使用的数据

• 核心模型数据

八、内存泄漏 vs 野指针

内存泄漏

• 对象无法释放

• 内存持续增长

• 常见原因:循环引用

野指针

• 对象已释放

• 指针仍访问

• EXC_BAD_ACCESS

泄漏是“活着但没用”,野指针是“死了还用”

九、工具

1️⃣ Instruments

Leaks

• 查循环引用

• 非 ARC / CoreFoundation 泄漏

Allocations

• 内存增长趋势

• 对象生命周期

Zombies(调试)

• 捕获野指针

• 定位已释放对象访问

2️⃣ Xcode Memory Graph

• 一眼看引用链

• block / delegate / timer 一清二楚

1️⃣ C 结构体不受 ARC 管理

AVFrame *frame = av_frame_alloc();

👉 必须:

av_frame_free(&frame);

2️⃣ CVPixelBuffer / OpenGL 资源

• 必须手动释放

• 否则 GPU 内存泄漏(系统更狠,直接 kill)

转载请注明:iTwinkle的博客 » iOS内存管理