iOS 内存管理进阶
1. 基础知识
- 可执行文件的内存结构
- .bss区,未初始化的全局变量和静态变量
- .data区,初始化的全局变量和静态变量
- stack区,临时变量,函数
- heap区,对象,在所有的线程,共享库,和动态加载的模块中被共享使用
1.1 全局变量和static变量区别
(static分两种,函数外或者函数内)
- 主要在作用域上,全局变量不仅可以在当前文件中使用,static函数外类型只能在当前文件使用
- static函数内(外也一样)变量会保存上一次修改的值
1.2 extern关键字
表示其他文件可以引用,与static意义正好相反
1.3 栈和堆内存分配与释放
名称 | 地址连续 | 分配/释放者 | 大小 |
---|---|---|---|
栈 | 是一块连续内存,不会出现内存碎片 | 编译器分配/释放,效率较高 | 最大容积固定 |
堆 | 不连续内存,容易出现内存碎片 | 由程序员负责分配/释放,容易内存泄漏 | 最大容积受虚拟内存影响 |
1.4 内存分配
- 虚拟地址 将程序代码或者数据分成若干个段(),每个段都有段名称+段内相对地址
- 逻辑地址 段内相对地址(相对概念)
- 物理地址 顾名思义,虚拟地址,在内存条上的物理映射
1.5 页面置换算法
LRU、FIFO
上边内容参考:内存管理
2. iOS 内存管理知识
- MRC
- ARC
- 属性关键字
- 变量关键字
- autoreleasepool
- dealloc
- 检测内存泄漏
3. 详细说明
- MRC
1.1 原则:谁创建谁释放,谁引用释放
(换句话说,谁修改了对象的引用计数,谁就负责释放)
NSObject *a = [[NSObject alloc] init]; //引用计数为1
[a release];
1.2 引用计数
- alloc/init new copy mutableCopy会修改对象引用计数+1
- release 会引用计数-1
- autorelease 当autoreleasepool drain时,会对加入其中的对象调用release方法,然后引用计数-1
1.3 MRC下property setter/getter方法
- (void)setData:(NSData *)data{
if(_data != data){ //判断是不同对象再release,否则可能会先release,导致野指针
[_data release]; // 释放之前对象
_data = [data retain]; // 引用新对象,引用计数+1
}
}
- (NSData *)data{
return [[_data retain] autorelease]; //调用retain保证对象不被释放,调用autorelease保证对象在合适时机释放,这样调用方不需要负责释放
}
1.4 使用与不使用临时变量区别
@property (nonatomic, retain) NSObject *property;
...
NSObject * a = [[NSObject alloc] init];
self.property = a;
[a release]; //1. 对象执行结束后,对象引用计数为1
self.property = [[NSObject alloc] init]; // 2. 语句执行完毕后,对象引用计数为2
...
- (void) dealloc{
[_property release]; //若为1情况,release之后,引用计数为0,正常释放;若为2情况,release之后,引用计数为1,无法释放
_property = nil;
}
1.5 retained return value和unretained return value
- alloc/init new copy mutableCopy 这些方法创建的都是retained return value,需要负责释放对象
- 其他工厂方法创建的都是unretained return value,不需要负责释放对象,如[NSArray array]方法,原因是该方法返回的对象是autorelease的,由autoreleasepool负责释放
- ARC
懂了MRC,ARC就非常容易理解,无非就是原来需要手动写release的地方,由编译器在编译时帮我们写了,让我们专注于业务代码,不care释放问题
2.1. 知道编译器插入了什么代码
(这个地方蛮复杂的,因为还有很多优化),可以考虑在MRC下需要怎么释放,然后编译器就加了那部分代码
NSArray *array = [[NSArray alloc] init];
self.property = array;
// [array release]; //编译器会加入该行代码
2.2 ARC下函数的返回值
此处可参考clang文档: retained-return-values
总结来说就是,对于retained return value,其实就是MRC下alloc/init new copy mutableCopy这些方法创建的对象,编译器会retain,然后再release
对于unretained return value,编译器会做优化,在最坏的情况下使用autorelease,正常情况就是延长生命周期,然后在合适时间释放。
(此处解释不太透彻,需要生成编译之后源码,直接看源码才能清晰)
3. 属性关键字
3.1 总共有6个,分别是:
assign weak unsafe_unretained
retain strong copy
3个不修改引用计数的,3个修改引用计数的(copy也没有,但是有一个新的指针,可以理解成多了个引用,其实引用计数没变)
3.2 几个区别
3.2.1 weak 与 assign unsafe_unretained
都不修改引用计数,
weak对象,当对象引用计数为0时,指向该对象的指针变量会自动为nil,不会出现野指针,unsafe_unretained不会自动置为nil
assign 还可以修饰基础数据类型,其他两个不可以
3.2.2 strong 与 retain copy
strong与retain基本是一样的,只有一个区别
strong修饰的block会被copy,retain修饰的不会
copy 会生成一个新的指针变量指向原对象,一般NSString常用
4. 变量关键字[ARC下有效]
有四个,__unsafe__unretained __weak __strong __autoreleasing (__block特殊)
4.1 __strong,默认关键字,不过不会修改对象引用计数,只是表示一种owner关系
NSMutableArray * str = [[NSMutableArray alloc] init]; //不会崩溃,默认__strong
NSMutableArray * __unsafe_unretained str = [[NSMutableArray alloc] init]; //会崩溃,没有
4.2 __autoreleasing
一般用在二级指针和id *类型的对象(其实还是二级指针)
如NSError ** __autoreleasing error; 一般作为函数参数声明使用
(此处理解还不透彻)
4.3 __unsafe__unretained
能分析透彻下边代码;
{}表示,代码只在{}域内有效
id __unsafe_unretained obj1 = nil;
// @autoreleasepool{ //打开autoreleasepool会崩溃
{
// id obj0 = [[NSMutableArray alloc] init]; //会崩溃
//[NSMutableArray array] 会崩溃原因是:
//objc_autoreleaseReturnValue函数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调用方在调用了方法或函数后紧接着调用objc_retainAutoreleasedReturnValue函数,那么就不将返回的对象注册到autoreleasepool中,而是直接传递到方法或函数的调用方
// id obj0 = [NSMutableArray array]; // 会崩溃
id obj0 = [NSMutableArray arrayWithObjects:@"234", nil]; //不会崩溃,内部有retain
[obj0 addObject:@"obj"];
obj1 = obj0;
NSLog(@"obj0 = %@", obj0);
}
// }
5. autoreleasepool
5.1 对象释放时机
出了{}域;
runloop每次循环结束
5.2 函数返回值
函数的返回值一般都是autorelease的
5.3 好处
在ARC下,可以让对象更快释放,其实相当于在{}内对每个对象调用了release方法,不用等到runloop走完再释放了
@autoreleasepool对[[obj alloc] init]生成的对象无效,因为只有[obj autorelease]的对象才会加入autoreleasepool
5.4 与指针置为nil对比
指针置为nil并不会释放对象,只是把指针变量置为空了,对象引用计数并没有受到影响
6. dealloc
6.1 变量置为nil
ARC下dealloc中一般是不用手动把对象置为nil的,编译器自动帮我们做了
6.2 ARC下dealloc中不要调super
MRC下[super dealloc]放在最下边一行,先让子类对象释放完毕
6.3 MRC下dealloc中变量释放使用实例变量
不要使用[self.property release]这种方式,因为防止父类和子类都重写了setter方法,那么子类中先释放,子类变成空,到父类释放时,还是要调用子类的setter方法,这时候子类已经不存在了。[经过我验证,这个情况没有发生,以后再探究]
7. 检测内存泄漏思路
7.1 使用工具
使用Leaks检测释放的内存,野指针之类
使用Allocations检测废弃的内存,有些无用的对象内存没有释放。[这个还没用过]
7.2 常见内存泄漏情况[我遇到的一些]
调用c方法时,要尤其注意
- runtime操作,class_copyIvarList方法,用完后未调用free
- 在类方法中,使用[self alloc] init],换成类名
- 使用sqlite3_exec方法,调用sqlite3_free方法
- 使用NSUrlSession,在completionBlock中或者comlete delegate中,调用finishTaskAndInvalidate方法
- NSTimer及时关闭
- performSelector
- oc和c互调
补充的一些,参考LNLeaksFinder
- NSNotificationCenter Block
7.3 代码检测的思路
得借用runtime,我想想