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

一、iOS 内存管理

iOS 内存管理,或者说任何一门语言的内存管理,都是程序员必须深刻理解,也是各种面试中最常见的话题。
iOS 内存管理中,在iOS 5以前,需要手动管理内存,即MRC(Manual Reference Counting)。
2011年,苹果推出了ARC(Automatic Reference Counting)技术,程序员不再需要手动管理内存,对开发效率的提升以及App的稳定性都有了显著提升。
不管是MRC,还是ARC,苹果内存管理的本质保持不变——就是OC对象的引用计数的管理。引用计数的概念同C++的智能指针概念很类似,编译期以及运行时会帮助我们自动来维护一个对象的生与死
所以说,引用计数是iOS 内存管理的根基所在,所有和内存管理相关的最后都是引用计数的问题。

二、内存布局

在进入iOS内存管理的具体技术细节之前,我们需要先了解各种各样变量以及对象在内存中的布局。对此进行了下面测试(在iPad Pro真机上测试,不同设备,每次测试结果可能有差异),对应代码--内存布局
notion image
notion image
notion image
由此,我们绘制了如下简单的内存布局图:
下图中,有一个地方需要注意:打印出的对象地址,即分配在堆上的地址,其实比栈地址更大。但在下图中则不是这样。下图描述的是一般性规则,但在不同的操作系统和编译期实现时,则各有不同,另外在堆中分配地址虚拟内存往往更大。
notion image
当然,OC语言源于C,其内存布局是类似的,如下是C语言对于内存中布局的一张图(by Iran 来源网络):
notion image
图中更为详细对部分段进行阐述:
  • .bss:存放未初始化数据的全局变量,以及static修饰的变量;
  • .data:存放已经初始化的的全局变量,以及static修饰的变量;
  • .text:存放代码,以及const等常量,这种常量包括const修饰符所修饰的,以及常量字符串。

三、内存管理的技术

3.1 Tagged Pointer

Tagged Pointer技术,就是在分配给对象的指针里,直接存储该对象的数据,并且以标记来区分是什么类型的数据。
使用Tagged Pointer技术的对象,没有引用计数这回事,不需要进行retain、release等相关操作。甚至在超过变量的作用域之后,仍然能访问。
可以使用Tagged Pointer技术的类有:NSNumberNSStringNSDate等。比如NSNumber在内存中存储如下:
notion image
将在下一篇中内存管理(二)Tagged Pointer,详细介绍该技术。

3.2 优化的isa指针

在64位设备推出后,苹果发现64位对象的isa指针是很浪费的,所以针对其进行了改良,改良结果如下:
notion image
上面就是优化过的isa指针,优化过的isa指针具备存储引用计数的作用,利用其高19位extra_rc来存储引用计数。
但是如果extar_rc不够存储的话,就需要将引用计数存入一个叫Side Table的数据结构中。
notion image
如果还不够,so sorry,你该反思下为什么会出现这种奇葩的事情。

3.3 引用计数

说回iOS 内存管理的根基——引用计数。
notion image
针对上面引用计数,NSObject中提供了对应的方法来对其进行控制:
notion image

对象的生命周期

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

四、属性

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

4.1 什么是属性

属性(property)是Objective-C语言的一个特性,并且用@property进行声明。
使用@property声明的变量,编译期将会自动为它生成对应的读写方法,即setter与getter方法。并且,允许开发者指定@property的参数,包括原子性、内存管理语义一系列特质,根据不同的特质,编译期将会对settergetter做不同的实现。
属性本质可简单归结于:属性=成员变量+setter+getter,但又不止于此,针对属性特质的丰富变化,才是开发者的利器。

4.2 @property 特质

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

五、内存问题

苹果为开发者提供了如此方便的计数,开发者又是如此的优秀。怎么会出问题。
内存问题还是会有的,一般分为两位:访问坏内存以及循环引用。

5.1 访问坏内存

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

5.1.1 野指针

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

5.1.2 空指针

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

5.2 内存泄漏

5.2.1 内存泄漏

内存泄漏,是指没有释放掉不再引用的对象。例如在MRC下:
但是,针对上面这种情况,在ARC下,编译期会自动帮我们释放。
在ARC下,另外一种更为常见的内存泄漏——循环引用。

5.2.2 循环引用

循环引用,当两个不同的对象各有一个强引用指向对方,那么就产生了循环引用,当然三个、四个对象产生的环循环引用也是同样的。
可以简单理解为A引用B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。
notion image
我们将在后面的一篇内存管理(七)循环引用 详述该问题。

5.3 分析

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

5.3.1 僵尸对象的监听

notion image

5.3.2 Instruments

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

5.3.3 FBMemoryProfiler

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

参考

链接
  1. Advanced Memory Management Programming Guide
  1. Memory Management Programming Guide for Core Foundation
  1. Automatic Reference Counting
  1. Objective-C Automatic Reference Counting (ARC)
  1. FBMemoryProfiler
示例代码
  1. 内存布局