内存管理(一)引入

本文将会先对iOS 内存管理涉及到一些基础的概念、技术,进行简单描述,我们将在后面系列篇章中对相关知识点进行更为深入的探讨。

一、iOS 内存管理

​ iOS 内存管理,或者说任何一门语言的内存管理,都是程序员必须深刻理解,也是各种面试中最常见的话题。

​ iOS 内存管理中,在iOS 5以前,需要手动管理内存,即MRC(Manual Reference Counting)。

​ 2011年,苹果推出了ARC(Automatic Reference Counting)技术,程序员不再需要手动管理内存,对开发效率的提升以及App的稳定性都有了显著提升。

​ 不管是MRC,还是ARC,苹果内存管理的本质保持不变——就是OC对象的引用计数的管理。引用计数的概念同C++的智能指针概念很类似,编译期以及运行时会帮助我们自动来维护一个对象的生与死

​ 所以说,引用计数是iOS 内存管理的根基所在,所有和内存管理相关的最后都是引用计数的问题。

二、内存布局

​ 在进入iOS内存管理的具体技术细节之前,我们需要先了解各种各样变量以及对象在内存中的布局。对此进行了下面测试(在iPad Pro真机上测试,不同设备,每次测试结果可能有差异),对应代码–内存布局

image-20190106190628366

由此,我们绘制了如下简单的内存布局图:

下图中,有一个地方需要注意—我们打印出的对象地址,即分配在堆上的地址,其实比栈地址更大。但在下图中则不是这样。下图描述的是一般性规则,但在不同的操作系统和编译期实现时,则各有不同,另外在堆中分配地址虚拟内存往往更大。

image-20190106190643214

当然,OC语言源于C,其内存布局是类似的,如下是C语言对于内存中布局的一张图(来源网络,侵删):

virtual-memory

图中更为详细对部分段进行阐述:

  • .bss:存放未初始化数据的全局变量,以及static修饰的变量;
  • .data:存放已经初始化的的全局变量,以及static修饰的变量;
  • .text:存放代码,以及const等常量,这种常量包括const修饰符所修饰的,以及常量字符串。

三、内存管理的技术

3.1 Tagged Pointer

Tagged Pointer技术,就是在分配给对象的指针里,直接存储该对象的数据,并且以标记来区分是什么类型的数据。

​ 使用Tagged Pointer技术的对象,没有引用计数这回事,不需要进行retain、release等相关操作。甚至在超过变量的作用域之后,仍然能访问。

​ 可以使用Tagged Pointer技术的类有:NSNumberNSStringNSDate等。比如NSNumber在内存中存储如下:

image-20190103145741152

将在下一篇中内存管理(二)Tagged Pointer,详细介绍该技术。

3.2 优化的isa指针

在64位设备推出后,苹果发现64位对象的isa指针是很浪费的,所以针对其进行了改良,改良结果如下:

image-20181212195446353

​ 上面就是优化过的isa指针,优化过的isa指针具备存储引用计数的作用,利用其高19位extra_rc来存储引用计数。

​ 但是如果extar_rc不够存储的话,就需要将引用计数存入一个叫Side Table的数据结构中。

image-20190106192019126

​ 如果还不够,so sorry,你该反思下为什么会出现这种奇葩的事情。

3.3 引用计数

说回iOS 内存管理的根基——引用计数。

image-20190106192212112

针对上面引用计数,NSObject中提供了对应的方法来对其进行控制:

image-20190106193844047

3.4 对象的生命周期

针对上面引用计数的阐述,相信大家对对象的引用计数都有一个基本的了解,那么对象的引用计数又是如何与对象的生命周期关联的呢?

image-20190106194032254

四、属性

​ 在开发中,属性太常用了,以至于无属性,不会写代码。

4.1 什么是属性

​ 属性(property)是Objective-C语言的一个特性,并且用@property进行声明。

​ 使用@property声明的变量,编译期将会自动为它生成对应的读写方法,即setter与getter方法。并且,允许开发者指定@property的参数,包括原子性、内存管理语义一系列特质,根据不同的特质,编译期将会对settergetter做不同的实现。

​ 属性本质可简单归结于:属性=成员变量+setter+getter,但又不止于此,针对属性特质的丰富变化,才是开发者的利器。

4.2 @property 特质

在这里,我们只说明与内存管理相关的属性特质,如下:

image-20190106193548283

五、问题

​ 苹果为开发者提供了如此方便的计数,开发者又是如此的优秀。怎么会出问题。

​ 问题还是会有的,一般分为两位:访问坏内存以及循环引用。

5.1 访问坏内存

访问坏内存,也分为两部分:

5.1.1 野指针

内存地址的对象已经释放后,再进行访问,就会产生的野指针问题,可能会引起崩溃;

image-20190106194654990

5.1.2 空指针

访问的内存地址的对象释放了,也置为nil了。再次访问拿不到任何结果,产生空指针问题。

image-20190106194756397

5.2 内存泄漏

5.2.1 内存泄漏

内存泄漏,是指没有释放掉不再引用的对象。例如在MRC下:

1
2
3
4
5
- (void)test
{
// 内存泄漏:该释放的对象没有释放
BFPerson *person = [[BFPerson alloc] init];
}

但是,针对上面这种情况,在ARC下,编译期会自动帮我们释放。

在ARC下,另外一种更为常见的内存泄漏——循环引用。

5.2.2 循环引用

​ 循环引用,当两个不同的对象各有一个强引用指向对方,那么就产生了循环引用,当然三个、四个对象产生的环循环引用也是同样的。

​ 可以简单理解为A引用B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。

image-20190106201919884

我们将在后面的一篇内存管理(七)循环引用-占位 详述该问题。

5.3 分析

​ 我们可以通过Xcode提供或其他工具来分析以上问题。

5.3.1 僵尸对象的监听

image-20190106195228883

5.3.2 Instruments

如下图,Instruments提供了一系列的工具来分析App的方方面面,图中圈起来的跟内存相关的工具。

image-20190106195128613

5.3.3 FBMemoryProfiler

facebook出品的内存检测工具,GitHub地址

参考

链接

  1. Advanced Memory Management Programming Guide
  2. Memory Management Programming Guide for Core Foundation
  3. Automatic Reference Counting
  4. Objective-C Automatic Reference Counting (ARC)
  5. FBMemoryProfiler

示例代码

  1. 内存布局