主要内容:
- 内存区域划分
- 内存管理/引用计数
-
MRC
手动管理引用计数 -
ARC
自动引用计数 - 内存泄漏问题
- 野指针问题
一、内存区域划分
程序在分配内存时,主要分为:栈区、堆区、静态区、常量区、代码区;
内存区域 | 具体说明 |
---|---|
栈区 | 存放局部变量的值,系统自动分配和释放; 特点:容量小,速度快,有序 |
堆区 | 存放通过malloc 系列函数或new 操作符分配的内存,如对象;一般由程序员分配和释放,如果不释放,则出现内存泄露; 特点:容量大,速度慢,无序; |
静态区 | 存放全局变量和静态变量(包括静态局部变量和静态全局变量); 当程序结束时,系统回收; |
常量区 | 存放常量的内存区域; 程序结束时,系统回收; |
代码区 | 存放二进制代码的区域 |
从上述分类上看,我们在开发过程中主要涉及的是堆上内存的管理。通常,我们创建一个对象的代码如下:
NSObject *obj = [[NSObject alloc] init];
上述代码创建了一个NSObject
类型的指针obj
和一个NSObject
类型的对象。obj
指针存在栈上,而其指向的对象则是在堆上。这种对象也称之为堆对象。
二、内存管理/引用计数
无论是MRC
还是ARC
环境,Objective-C
都采用引用计数来管理内存;每个对象都有一个引用计数器,任何时候指向对象的指针个数和对象的引用计数相等,当一个对象的引用计数为0
的时候将会被释放;
OC
管理内存涉及到对象的"生成"、"持有"、"释放",MRC
需要调用对应的方法来管理引用计数,而ARC
则是自动管理引用计数,无需再调用这些内存管理的方法。虽然两者管理内存的形式不同,但是它们都遵循相同的内存管理规律,内容如下:
- 自己生成的对象,自己所持有;
- 非自己生成的对象,自己也能持有;
- 不再需要自己持有对象时释放;
- 非自己持有的对象无法释放;
三、MRC手动管理引用计数
MRC
,即手动管理引用计数。当我们通过alloc
、retain
等方法持有对象后,也必须有相应的release
或者autorelease
将其释放。总结对象操作与Objective-C
内存方法对应关系如下:
对象操作 | OC方法 |
---|---|
生成并持有对象 | 以alloc /new /copy /mutableCopy 等名称开头方法 |
持有对象 | retain 方法 |
释放对象 | release 方法 |
废弃对象 | dealloc 方法 |
1.自己生成的对象,自己所持有/非自己生成对象,不持有
id obj = [[NSObject alloc] init]; //自己生成并持有对象
id obj1 = [NSMutableArray array]; //取得非自己生成的对象,但不持有对象
OC
中使用alloc
、new
、copy
、mutableCopy
这些名称开头的方法意味着自己生成对象并持有,否则就是非自己生成的对象不持有。如上源码,使用NSObject
类的alloc
类方法就能自己生成并持有对象,指向生成并持有对象的指针被赋值给了obj
。
通过自定义方法来理解这两种创建对象方法的区别(系统方法也是类似的实现),测试代码如下:
//以alloc开头的方法
- (id)allocObject {
id obj = [[NSObject alloc] init];
return obj;
}
- (id)object {
id obj = [[NSObject alloc] init];
[obj autorelease]; //用该方法,可以使取得的对象存在,但是自己不持有对象
return obj;
}
autorelease
即自动释放,对象已经加入自动释放池,所以获取对象并不持有;涉及到的自动释放池的内容会在后续详细总结。
注意:生成并持有对象的的方法一定是驼峰拼写来命名的方法,如alloc
、allocMyObject
等方法;相反allocate
、mutableCopyed
就不属于这类方法;
2.非自己生成的对象,自己也能持有
id obj1 = [NSMutableArray array]; //取得非自己生成的对象,但不持有对象
[obj retain]; //通过retain方法,持有了对象
源代码中,NSMutableArray
类对象被赋值给变量obj
,但是变量obj
自己不持有该对象。使用retain
方法后可以持有对象。
3.不再需要自己持有对象时释放
自己持有的对象,一旦不需要,持有者有义务释放该对象,释放对象使用release方法。
id obj = [[NSObject alloc] init]; //自己生成并持有对象
[obj release]; //释放自己持有的对象
NSLog(@"%@",obj); //已经释放,再次使用会崩溃
虽然指向对象的指针依然保留在变量obj
中,看似可以访问,但对象一经释放就绝不可再访问。
4.非自己持有的对象无法释放
在应用程序中释放非自己持有的对象就会造成崩溃,使用代码演示如下:
//情况1:释放完不再需要的对象后再次释放,访问了已经废弃的对象而崩溃!
id obj = [[NSObject alloc] init];
[obj release];
[obj release];
//情况2:取得自己并不持有的对象对其释放,释放了非自己持有的对象而崩溃!
id obj = [[NSMutableArray array];
[obj release];
四、ARC自动引用计数
ARC(Automic Reference Counting)
,即自动引用计数;这是iOS5
推出的新特性,iOS4.3
也支持ARC
,只是不能使用weak
。ARC
不再需要使用类似retain
、release
的操作来持有或者释放对象,从而大大提高了开发效率;
1.ARC使用条件
-
Xcode4.2
或以上版本 - 使用
LLVM
编辑器3.0
或以上版本 -
Xcode
编译器选项中设置ARC
有效
2.ARC基本原理
-
ARC下
的编译器会在代码编译阶段合适的位置,自动加入retain/release/autorelease
的操作; -
ARC
的规则:只要还有一个强引用指针指向对象,对象就会保存在内存中; -
ARC
中使用strong
和weak
关键字来修饰对象;strong
表示强引用,对应MRC
下的retain
;weak
表示弱引用,对应原来的assign
,不同的是当对象被释放的时候,对象weak
指针自动赋值为nil
,从而不会引发野指针错误;
3.ARC所有权修饰符
ARC
有效时,OC
处理id
类型和对象类型必须附加所有权修饰符。所有权修饰符一共有四种:
__strong
__weak
__unsafe_unretained
__autoreleasing
__strong修饰符:
__strong
是id
类型和对象类型默认的所有权修饰,表示对对象的"强引用";当对象没有任何一个强引用指向它的时候,对象将被释放。
__weak修饰符:
1.__weak
与__strong
修饰符的作用相反,表示弱引用,不会增加引用计数;
2.当对象被释放后,所有指向它的弱引用都会被置为nil
,这样避免了野指针问题。
3.__weak
修饰符常用于解决循环引用问题;
4.__weak
只能用于iOS5
以上版本,更早的版本只能使用__unsafe_unretained
修饰符。
__unsafe_unretained修饰符:
1.__unsafe_unretained
提供弱引用,与__weak
作用类似;
2.__unsafe_unretained
不能在对象释放后自动置为nil
,易产生野指针问题;
3.__unsafe_unretained
可用于iOS5
之前版本,为兼容ARC
弱引用而引入;
__autoreleasing修饰符:
将对象赋值给附有__autoreleasing
修饰符的变量,
等同于在MRC
下调用对象的autorelease
方法,即对象被注册到autoreleasepool
ARC
环境不能使用NSAutoreleasePool
类也不能调用autorelease
方法,代替它们实现对象自动释放的是@autoreleasepool块
和__autoreleasing
修饰符;两种环境下的使用情况类比如下图:
如图所示,@autoreleasepool
块替换了NSAutoreleasePoool
类对象的生成、持有及废弃这一过程。而附有__autoreleasing
修饰符的变量替代了autorelease
方法,将对象注册到了autoreleasepool
;
但事实上,显式使用__autoreleasing
修饰符的情况非常少见,这主要是因为ARC
的很多情况下,即使是不显式的使用__autoreleasing
,也能实现对象被注册到释放池中。这其中就包括以下的几种情况:
- 编译器检查方法名是否以
alloc/new/copy/mutableCopy
开始,如果不是则自动将返回对象注册到autoreleasepool
; - 访问附有
__weak
修饰符的变量时,实际上必定要访问注册到autoreleasepool
的对象; - id的指针或对象的指针在没有显式地指定修饰符时候,会被默认附加上
__autoreleasing
修饰符;
4.ARC属性修饰符
ARC
中的所有权修饰与属性修饰符存在着对应关系,如果不一致还会引起编译错误。总结两者的对应关系如下:
属性修饰符 | 所有权修饰符 |
---|---|
assign |
__unsafe_unretained |
copy |
__strong (但是赋值的是被复制的对象) |
retain |
__strong |
strong |
__strong |
unsafe_unretained |
__unsafe_unretained |
weak |
__weak |
以上各种属性只有copy
不是简单的赋值,它赋值的是通过NSCopying
接口的copyWithZone:
方法复制赋值源生成的对象。
5.ARC管理内存的规则
- 不能使用
retain/release/retainCount/autorelease
内存管理方法; - 不能使用
NSAllocateObject/NSDeallocateObject
方法; - 必须遵守内存管理的方法命名规则;
- 不能显式调用
dealloc
方法,如[super dealloc]
; - 使用
@autoreleasepool
块代替NSAutoreleasePool
; - 不能使用区域(
NSZone
); - 对象类型变量不能作为C语言结构体(
struct/union
)的成员; - 显式转换
id
和void *
6.必须遵守内存管理的方法命名规则
MRC
下,用于对象生成/持有的方法必须遵守alloc
、new
、copy
、mutableCopy
的命名规则。以这些名称开始的方法在返回对象时,必须返回给调用方所应当持有的对象。这在ARC
环境下的规则一样。只是ARC
下关于init
开发的方法规则要更加严格了:
- 必须是实例方法,且返回对象;
- 返回对象应该是
id
类型或该方法声明类的对象,抑或该类的超类或子类; - 该返回类型不注册到
autoreleasepool
上; - 基本上,
init
方法只是对alloc
方法返回值的对象进行初始化处理并返回对象;
7.显式转换id和void *
这里说到的其实就是Core Foundation
和Foundation
两者之间的转换。
Core Foundation
是由C
语言实现的,而Foundation
由Objective-C
实现,两者可以相互转换。
MRC
不存在显式转换的问题,因为本来就是手动管理内存。但是为了在ARC
也能实现对Core Foundation
对象的自动内存管理,我们就必须将其与Objective-C
对象的转换。Objective-C
中提供了三个关键字__bridge
、__bridge_retained
、__bridge_transfer
来实现转换。
情况1:__bridge转换
/*
MRC代码下,将id变量直接强制转换void*正常,但ARC下报错
id obj = [[NSObject alloc] init];
void *p = obj;
*/
//ARC下代码使用__bridge实现单纯的赋值转换
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)(obj);
__bridge
可实现Objective-C
对象和Core Foundation
对象的相互转换;但是其安全性与赋值给__unsafe_unretained
修饰符相近,甚至会更低。如果管理时不注意赋值对象的所有者,就容易产生野指针错误导致程序崩溃。
情况2:__bridge_retained转换
id obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)(obj);
/*相当于MRC代码:
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];
*/
__bridge_retained
转换可使要转换的变量也持有所赋值的对象。此操作类似于retain
。上述代码中变量obj
和变量p
同时持有对象。
情况3:__bridge_transfer转换
id obj = (__bridge_transfer id)p;
/*相当于MRC代码:
id obj = id(p)
[obj retain];
[(id)p release];
*/
__bridge_transfer
转换提供与__bridge_retained
相反的动作,被转换的变量所持有的对象在该变量被赋值给转换的目标后随之释放。此操作与release
相似。
五、内存泄漏问题
内存泄露就是本该废弃的对象在超出其生命周期后继续存在。导致系统内存浪费、程序运行速度减慢甚至系统崩溃等严重后果;
总结常见的内存泄露的异常情况如下:
-
AFNetworking
循环引用(未使用单例或者没有调用销毁NSURLSession
的方法; -
Block
循环引用 -
delegate
循环引用 -
NSTimer
循环引用 - 创建的非
OC
对象内存,在使用完毕后未手动释放; - 循环操作创建大量临时对象,导致内存导致内存暴涨;
- 地图类处理,使用完毕后未及时销毁地图相关组件对象
六、野指针问题
野指针指针就是指向一个已经删除对象或者访问受限内存区域的指针;
注意:野指针不是nil
指针,而是指向”垃圾”内存(不可用内存)的指针;
总结ARC
下常见的野指针异常情况如下:
参考链接:
1.Foundation对象与Core Foundation对象间的转换
2.iOS几种容易忽略的内存泄漏方式
3.ARC下野指针常见写法