【iOS开发】—— iOS内存管理
文章目录
- 1. iOS内存分配区域
- 1.1 栈区
- 1.2 堆区
- 1.3 常量区
- 1.4 全局区/静态区
- 1.4.1 static静态变量
- 1.4.2 extern全局变量
- 1.5 代码区
- 2. iOS的编译链接
- 2.1 预处理
- 2.2 编译
- 2.3 汇编
- 2.4 链接
- 3. 引用计数和MRC
- 3.1 内存管理的思考方式(四个基本法则)
- 3.1.1 自己生成的对象,自己持有
- 3.1.2 非自己生成的对象,自己也能持有
- 3.1.3 不再需要自己持有的对象就将其释放
- 3.1.4 非自己持有的对象无法释放
- 3.2 内存管理基本表格
- 3.3 autorelease
- 使用场景
- 3.4 retainCount
- 4. ARC自动引用计数
- 4.1 所有权修饰符
- 4.1.1 __strong修饰符
- 4.1.2 __weak修饰符
- 4.1.3 __unsafe_unretained修饰符
- 4.1.4 __autoreleasing修饰符
- 5.相关问题
- 5.1 ARC在编译和运行期间做了什么?
- 5.2 一些规则
- 5.3 属性关键字和所有权修饰符
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];
3.1.3 不再需要自己持有的对象就将其释放
不再需要自己持有的对象就将其使用release释放。
- 释放自己生成并持有的对象
//自己持有对象
id obj = [[NSObject alloc] init];
//释放对象
//指向对象的指针仍然被保留在变量obj中,貌似可以访问,但对象一经释放绝对不可访问
[obj release];
- 释放非自己生成但持有的对象
id obj = [NSMutableArray array];
//持有该对象
[obj retain];[obj release];
注意:(以下代码在MRC环境下运行)
运行后应该出现的情况是程序崩溃,因为在array创建后引用计数变为1,之后使用release是引用计数减1,引用计数变为0,然后将NSMutableArray类对象A内存块释放,在打印其地址时应该是程序崩溃,但是运行后程序不一定崩溃,是因为array指针依然存在并且指向那段内存,而内存还没有被占用,所以可以访问到,有时又会报错,相当于碰运气…
3.1.4 非自己持有的对象无法释放
如果不是自己持有的对象一定不能进行释放,倘若在应用程序中释放了非自己所持有的对象就会造成崩溃。
- 释放两次
例如:自己生成并持有,在释放完不再需要的对象之后再次释放(释放两次)
//自己生成并持有对象
id obj = [[NSObject alloc] init];
//释放对象
[obj release];
//释放对象
[obj release];
- 本就不持有
但是以上情况现在都不会崩溃,可能苹果修复了。。。
3.2 内存管理基本表格
对象操作 | Objective-C方法 | 引用计数 |
---|---|---|
生成并持有对象 | alloc/new/copy/mutableCopy方法 | +1 |
持有对象 | retain | +1 |
释放对象 | release | -1 |
废弃对象 | dealloc | 对象所占内存解除分配 并放回“可用内存池中” |
3.3 autorelease
autorelease即“自动释放”,类似于C语言中的局部变量。C语言的局部变量:程序执行时,若某局部变量超出其作用域,该局部变量自动废弃。autorelease像C语言的局部变量那样对待对象实例。当超出其作用域时,对象实例的release实例方法被调用,释放该变量。
前面介绍到了释放对象使用release。也可以使用autorelease,二者的区别是什么呢?
调用 autorelease 方法,就会把该对象放到离自己最近的自动释放池中(栈顶的释放池,多重自动释放池嵌套是以栈的形式存取的),即:使对象的持有权转移给了自动释放池(即注册到了自动释放池中),调用方拿到了对象,但这个对象还不被调用方所持有。当自动释放池销毁时,其中的所有的对象都会调用一次release操作。
其实也就是autorelease 方法不会改变调用者的引用计数,它只是改变了对象释放时机,不再让程序员负责释放这个对象,而是交给自动释放池去处理 。
autorelease 方法相当于把调用者注册到 autoreleasepool 中,ARC环境下不能显式地调用 autorelease 方法和显式地创建 NSAutoreleasePool 对象,但可以使用@autoreleasepool { }块代替(并不代表块中所有内容都被注册到了自动释放池中)。
对于所有调用过autorelease实例方法的对象,在废弃NSAutoreleasePool对象时,都将调用release实例方法。
使用场景
- 循环中创建了许多临时对象,在循环里使用自动释放池,用来减少高内存占用。
- 开启子线程的时候要自己创建自己的释放池,否则可能会发生内存泄漏。
3.4 retainCount
我们在MRC中,有时可能会想要打印引用计数,但retainCount方法并不是很有用,由于对象可能会处于自动释放池中,这会导致打印的引用计数并不精准,而且其他程序库也很有可能自行保留或释放对象,这都会扰乱引用计数的具体值。
4. ARC自动引用计数
4.1 所有权修饰符
ARC有效时,id类型和对象类型必须附加所有权修饰符(默认为__strong),修饰符有四种:
- __strong修饰符
- __weak修饰符
- __unsafe_unretained修饰符
- __autoreleasing修饰符
4.1.1 __strong修饰符
__strong修饰符是id类型和对象类型默认的所有权修饰符,表示强引用。
不论调用哪种方法,强引用修饰的变量会持有该对象,如果已经持有则引用计数不会增加。
持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。
{//因为变量obj为强引用,所以持有对象id __strong obj = [[NSObject alloc] init];
}
//超出作用域后强引用失效,对象所有者不存在,被自动释放
附有__strong修饰符的变量也可以互相赋值
id obj = [[NSObject alloc] init];
id obj2 = [[NSObject alloc] init];
id obj3 = nil;
obj3 = obj2;
obj2 = obj;
赋值的本质是强引用的转变。
附有__strong修饰符同__weak修饰符,__autorelease修饰符一起,可以保证将附有这些修饰符的自动变量初始化为nil,即:
id __strong obj1;
id __weak obj2;
id __autoreleasing obj3;
//等同于
id __strong obj1 = nil;
id __weak obj2 = nil;
id __autoreleasing obj3 = nil;
使用__strong可能会导致循环引用问题:
例如:
@interface MyObject : NSObject {id __strong object;
}
- (void)setObject:(id __strong) obj;
@end@implementation MyObject
- (instancetype)init {self = [super init];return self;
}- (void)setObject:(id __strong) obj {_object = obj;
}
@end
主函数
#import <Foundation/Foundation.h>
#import "MyObject.h"
int main(int argc, const char * argv[]) {{id test1 = [[MyObject alloc] init];//对象Aid test2 = [[MyObject alloc] init];//对象B[test1 setObject:test2];[test2 setObject:test1];}return 0;
}
我们可以看出以上代码中,持有对象A的是test1和test2的object;持有对象B的是test2和test1的object。test1和test2超出作用域后,强引用失效,自动释放对象,但是test1的object和test2的object分别持有着对象B和对象A,所有对象B和对象A并没有释放,这样就造成了内存泄漏。
4.1.2 __weak修饰符
在介绍__strong修饰符时,我们说到一个循环引用的问题,现在就可以利用__weak修饰符来解决这个问题。
id __weak obj = [[NSObject alloc] init];
但是不能像上面这么解决,原因是创建的对象如果弱引用的话,就没有持有该对象的变量了,就会被释放。所以应该像下面这么写:
id __strong sObj = [[NSObject alloc] init];
id __weak wObj = sObj;
让sObj持有它。
所以上面循环引用的代码就可以写成:
@interface MyObject : NSObject {id __weak object;
}
- (void)setObject:(id __strong) obj;
@end@implementation MyObject
- (instancetype)init {self = [super init];return self;
}- (void)setObject:(id __strong) obj {object = obj;
}
@end
这样修改后,test1持有对象A,test2持有对象B,而test1的object和test2的object并不持有对象A和对象B,超出作用域后,就没有变量再持有对象A和对象B,对象A和对象B就会被释放,就解决了循环引用的问题。
__weak还有一个作用,就是在持有某对象的弱引用,如果对象被废弃,弱引用将自动失效且为nil。
id __weak obj;{id obj0 = [[NSObject alloc] init];obj = obj0;NSLog(@"%@", obj);}NSLog(@"%@", obj);
在《Objective-C高级编程》中写到:附有__weak修饰符的变量,即是使用注册到自动释放池中的对象,但是我在测试时,并不是这样:
id obj = [[NSObject alloc] init];id __weak wObj = obj;NSLog(@"%@", wObj);NSLog(@"%@", wObj);NSLog(@"%@", wObj);NSLog(@"%@", wObj);NSLog(@"%@", wObj);NSLog(@"%@", wObj);_objc_autoreleasePoolPrint();
难道书中写错了? 我们改一种使用__weak修饰变量的方法:
这种情况下就和书中的结论不谋而合了。
每使用一次__weak对象,运行时系统都会将其指向的原始对象先retain,之后保存到自动释放池中。因此如果大量调用__weak对象,则会重复进行此工作。不仅耗费无意义的性能(重复存储同一对象),还会使内存在短时间内大量增长。
那NSLog为什么和书中的结论不一样呢?这里还有点不解。
4.1.3 __unsafe_unretained修饰符
正如其名,__unsafe_unretained修饰符是不安全的,且附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象。
同样,附有__unsafe_unretained修饰符不能直接使用。
与__weak修饰符不同的是:附有__weak修饰符的弱引用变量在对象释放时,会自动置为nil,但是__unsafe__unretained修饰符不会,所以一旦对象释放,则会成为悬垂指针,程序崩溃,所以__unnsafe_unretained修饰符的变量一定要在赋值的对象存在的情况下使用。
4.1.4 __autoreleasing修饰符
上面已经讲过ARC无效时autorelease的使用,在ARC有效时,可以写成这样:
@autoreleasepool {id __autoreleasing obj = [[NSObject alloc] init];
}
@autoreleasepool块即相当于上文的NSAutoreleasePool类生成、持有及废弃。
附有__autoreleasing修饰符相当于变量调用了autorelease方法。
以下为使用__weak修饰符的例子,虽然__weak修饰符是为了避免循环引用而使用的,但在访问附有__weak修饰符的变量时,实际上必定要访问注册到AutoreleasePool的对象
id __weak wObj = sObj;
NSLog(@"%@", [wObj class]);
//等同于
id __weak wObj = sObj;
id __autoreleasing tmp = wObj;
NSLog(@"%@", [tmp class]);
因为弱引用不持有,在访问对象的过程中对象有可能被废弃,要保证正常访问就需要将其注册到自动释放池中
一般情况下,id的指针或对象的指针会默认附加上__autoreleasing修饰符,即:
(NSError **)error
//等同于
(NSError * __autoreleasing *)error
然而,下面的源代码会有编译错误
NSError *error = nil;
NSError **pError = &error;
原因是所有权不同,在赋值时,所有权必须一致
NSError *error = nil;
NSError * __strong *pError = &error;
这样就对了。
5.相关问题
5.1 ARC在编译和运行期间做了什么?
1.在编译期,ARC会把互相抵消的retain、release、autorelease操作约简。
2.ARC包含有运行期组件,可以在运行期检测到autorelease和retain这一对多余的操作。为了优化代码,在方法中返回自动释放的对象时,要执行一个特殊函数。
5.2 一些规则
- 只能在ARC无效时使用retain/release/retainCount/autorelease等方法,ARC有效时也不能使用NSAllocateObject/NSDeallocateObject函数。
- 必须遵守内存管理的方法名规则,即:
- 以alloc/new/copy/mutableCopy开始命名的方法在返回对象时,必须返回给调用方所应当持有的对象。
- 以init开始命名的方法在返回对象时,返回的对象应为id类型或该方法声明类的对象类型,亦或是该类的超类或子类。
- 不要显示调用dealloc,因为dealloc无法释放不属于该对象的一些东西,比如:通知的观察者和KVO的观察者等。
- 使用@autoreleasepool块代替NSAutoreleasePool
- 不能使用区域NSZone。
- 对象性变量不能作为C语言结构体的成员。
- 显式转换id和void*。
5.3 属性关键字和所有权修饰符
【iOS开发】—— iOS内存管理相关推荐
- iOS开发ARC内存管理技术要点
本文来源于我个人的ARC学习笔记,旨在通过简明扼要的方式总结出iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点,所以不会涉及全部细节.这篇 ...
- iOS开发ARC内存管理
本文的主要内容: ARC的本质 ARC的开启与关闭 ARC的修饰符 ARC与Block ARC与Toll-Free Bridging ARC的本质 ARC是编译器(时)特性,而不是运行时特性,更不是垃 ...
- (0048)iOS开发之内存管理探究
注意网上很多人支招在ARC模式下可以利用_objc_rootRetainCount()或者CFGetRetainCount()取得 retainCount都是不准确的,特别是在对象拷贝操作之后你会发现 ...
- iOS/OS X内存管理(一):基本概念与原理
iOS/OS X内存管理(一):基本概念与原理 发表于21小时前| 1585次阅读| 来源CSDN| 8 条评论| 作者刘耀柱 移动开发iOSObjective-C内存管理内存泄露局部变量开发经验 a ...
- iOS/OS X内存管理(二):借助工具解决内存问题
上一篇博客<iOS/OS X内存管理(一):基本概念与原理>主要讲了iOS/OS X内存管理中引用计数和内存管理规则,以及引入ARC新的内存管理机制之后如何选择ownership qual ...
- iOS夯实:内存管理
最近的学习计划是将iOS的机制原理好好重新打磨学习一下,总结和加入自己的思考. 有不正确的地方,多多指正. 基本信息 Objective-C 提供了两种内存管理方式. MRR (manual reta ...
- iOS开发经验总结—内存管理
From: http://ihuby.info/2011/07/18/ios%E5%BC%80%E5%8F%91%E7%BB%8F%E9%AA%8C%E6%80%BB%E7%BB%93-%E5%86% ...
- iOS OC08,09_内存管理
//管理内存有三种方式,//1.是垃圾回收,java常见的管理内存的方法,系统来检測对象是否被使用,是否被释放//2.MRC手动管理引用计数,iOS管理内存的方式,程序猿通过手动的方式来管理对象是否被 ...
- iOS开发--- iOS编程浅析
1.简介 IOS是由苹果公司为iPhone.iPod touch和iPad等设备开发的操作系统. 2.知识点 1.IOS系统 iPhone OS(现在叫iOS)是iPhone, iPod touch ...
- iOS开发--iOS及Mac开源项目和学习资料
文/零距离仰望星空(简书作者) 原文链接:http://www.jianshu.com/p/f6cdbc8192ba 著作权归作者所有,转载请联系作者获得授权,并标注"简书作者". ...
最新文章
- git 还原版本方法
- opencv Mat 函数--CheckVector
- mysql 多表查询 优化_Mysql 多表联合查询效率分析及优化
- 查看Linux进程CPU过高具体的线程堆栈(不中断程序)
- 雷军晒3亿估值,意欲“收编”台湾硬件创业者?
- Python MySQL更新表
- springmvc拦截器配置
- 数组算法 中部删除数据 1202
- Visual Studio Code 编辑器使用
- 转载:常用CSS缩写语法总结
- python保存mat文件_python读取文件——python读取和保存mat文件
- 面向全场景模块化设计,京东智联云的服务器部署有多灵活?
- Refresh page
- 使用SVM分类器进行图像多分类
- 基于tushare和python的证券市场价格分析
- IE8 证书错误,导航已阻止,解决方法(selenium)
- 密码加密md5和加盐处理
- 连续竞价java_撮合引擎开发:开篇
- hdu5442 后缀数组
- linux 4g 拨号,4G模块Linux PPP拨号说明