1. iOS内存分配区域
常说的五大内存分配区域,指的是栈区、堆区、常量区、全局区、代码区。
1.1 栈区
是由编译器自动分配并释放的,主要用来存储局部变量、函数的参数等,是一块连续的内存区域,遵循先进后出(FILO)原则。一般在运行时分配。它的分配由高地址空间向低地址空间分配。
优点:因为栈是由编译器自动分配并释放的,不会产生内存碎片,所以快速高效。
缺点:栈的内存大小有限制,数据不灵活。
例如:下图,创建两个变量,存放在栈区,地址是递减4。
1.2 堆区
堆区是由程序员手动管理。 主要用来存放动态分配的对象类型数据。是不连续的内存区域。在MRC环境下,由程序员手动释放,在ARC环境下,由编译器自动释放。一般在运行时分配。它的分配是从低地址空间向高地址空间分配。
优点:灵活方便,数据适应面广泛。
缺点:需手动管理,速度慢、容易产生内存碎片。
例如:下图,创建两个对象,存放在堆区,地址递增。
1.3 常量区
常量区存放的就是字符串常量和基本类型常量。在编译时分配,程序结束后回收。
1.4 全局区/静态区
全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在.data
段,未初始化的全局变量和静态变量在相邻的.bss
区域,在编译时分配,程序结束后由系统释放。
1.4.1 static静态变量
- 全局静态变量
全局变量和全局静态变量的生命周期是一样的,都是在堆中的静态区,在整个工程执行期间内一直存在。
特点如下:
1)存储区:静态存储区没变(静态存储区在整个程序运行期间都存在);
2)作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。非静态全局变量的作用域是整个源程序(多个源文件可以共同使用); 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。
好处:
1)不会被其他文件所访问,修改;
2)其他文件中可以使用相同名字的变量,不会发生冲突。
- 局部静态变量
特点如下:
1)存储区:有栈变为静态存储区rw data,生存期为整个源程序,只能在定义该变量的函数内使用。退出该函数后, 尽管该变量还继续存在,但不能使用它;
2)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。
1.4.2 extern全局变量
==只是用来获取全局变量(包括静态全局变量)的值,不能用于定义变量。==先在当前文件查找有没有全局变量,没有找到,才会去其他文件查找。
全局静态变量与全局变量 其实本质上是没有区别的,只是存在修饰区别,一个static让其只能内部使用,一个extern让其可以外部使用。
1.5 代码区
代码区是在编译时分配,主要用于存放程序运行时的代码,代码会被编译成二进制存进内存的。
2. iOS的编译链接
在一个程序从代码到执行程序过程里,存在四个阶段:预编译、编译、汇编、静态链接。
2.1 预处理
预处理也被称作预编译(Prepressing),是将main.m
文件编译成mian.i
文件,指令如下:
clang -E main.m -o main.i
处理源代码文件中以#开头的预编译指令。
2.2 编译
编译过程就是把上面的main.i文件进行:词法分析、语法分析、静态分析,优化生成相应的汇编代码,最终生成main.s文件。,指令如下:
clang -S main.i -o main.s
名称 | 处理过程 |
---|---|
词法分析 | 把源代码的字符序列分割成一个个token(关键字、表示符、字面量、特殊符号),比如把标识符放到符号表里面。 |
语法分析 | 生成抽象语法树 AST,此时运算符号的优先级确定了;有些符号具有多重含义也确定了,比如“*”是乘号还是对指针取内容;表达式不合法、括号不匹配等,都会报错。 |
静态分析 | 分析类型声明和匹配问题。比如整型和字符串相加,肯定会报错。 |
中间语法生成 | CodeGen根据AST自上向下逐步翻译成LLVM IR(连接着编译器前端和编译器后端),并且对在编译期就可以确定的表达式进行优化,比如代码里面的a=1+3,可以优化成a=4。(假如开启了bitcode) |
目标代码生成与优化 | 根据中间语法生成依赖具体机器的汇编语言;并优化汇编语言。这个过程中,假如有变量且定义在同一个编译单元里,那么就给这个变量分配空间,确定变量的地址。假如变量或者函数不定义在这个编译单元里面,那就等到链接的时候才能确定地址。 |
2.3 汇编
这个过程就是把上面得到的main.s文件里面的汇编指令翻译成机器指令,最终生成等到main.o文件;指令如下:
clang -c main.s -o main.o
2.4 链接
这个过程就是将main.o编译成对应的Mach-O文件,也就是我们常说的可执行文件,指令如下:
clang main.o -o main
链接的本质就是把一个或多个目标文件和需要的库(静态库/动态库,如果需要的话)组合成一个文件(Mach-O可执行文件)。
3. 引用计数和MRC
在OC中,使用引用计数来进行内存管理。每个对象都有一个与其相对应的引用计数器,当持有一个对象,这个对象的引用计数就会递增;当这个对象的某个持有被释放,这个对象的引用计数就会递减。当这个对象的引用计数变为0,那么这个对象就会被系统回收。
当一个对象使用完没有释放,此时其引用计数永远大于1。该对象就会一直占用其分配在堆内存的空间,就会导致内存泄露。内存泄露到一定程度有可能导致内存溢出,进而导致程序崩溃。
3.1 内存管理的思考方式(四个基本法则)
3.1.1 自己生成的对象,自己持有
alloc\new\copy\mutableCopy 方法名开头来创建的对象意味着自己生成的对象只有自己持有。
id obj = [[NSObject alloc] init];
3.1.2 非自己生成的对象,自己也能持有
用 alloc / new / copy / mutableCopy 以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者。(比如 NSMutableArray 类的 array方法),但是我们可以通过retain来手动持有对象。
//取得的对象存在但不持有
id obj = [NSMutableArray array];
//持有该对象
[obj retain];