一、内存管理的?式
大家都去过图书馆,而图书馆里的书是可以借出的。我们来设想这样一个场景,大家都去借书,但是从来没有人去还书,那么最后,这个图书馆会因为无书可借而倒闭,每个人都没法再使用图书馆。计算机也是这样,当程序运行结束时,操作系统将回收其占用的资源。但是,只要程序运行就会占用资源,如果不进行清理已经不用的资源,资源最终将被耗尽,程序将崩溃。
学会内存管理我们就明白什么时候由你释放对象,什么时候你不能释放。C语言中通过malloc、calloc、realloc和free搭配对内存进行管理。但是,C中的内存管理极易引起内存泄露和野指针异常(有关C语言内存泄露的问题,不是本博文的重点,对此不做过多赘述。详情会在后续的博文中专门讲解C内存问题。),而Objective-C对此进行了优化。
iOS应用程序出现Crash(闪退),90%以上的原因是内存问题。在一个拥有数?个甚至是上百个类的工程里,查找内存问题极其困难。了解内存常见问题,能帮我们减少出错几率。内存问题体现在两个?面:内存溢出、野指针异常。
内存溢出
iOS给每个应?程序提供了一定的内存,?于程序的运行。iPhone 3GS内存30M左右,iPhone 5S内存80M左右。一旦超出内存上限,程序就会Crash。程序中最占内存的就是图?、?频、视频等资源文件。3.5寸非Retina(视网膜)屏幕(320*480)放一张全屏图片,占用字节数320*480*4(一个像素占4个字节,存放RGBA),即:600k Bytes。iPhone 3GS同时读取60张图片就会Crash。4寸屏幕(320*568),实际像素640*1136,程序存放一张全屏图片,占用字节数640*1136*4,即2.77M Bytes。iPhone 5S同时读取40张图片就会Crash。
野指针异常
对象内存空间已经被系统回收,仍然使?指针操作这块内存。野指针异常是程序crash的主要原因。代码量越?的程序,越难找出野指针的位置。了解内存管理,能帮我们提升程序性能,?大减少调试bug时间。
内存管理的?式
垃圾回收(gc):程序员只需要开辟内存空间,不需要用代码显示地释放,系统来判断哪些空间不再被使用,并回收这些内存空间,以便再次分配。整个回收的过程不需要写任何代码,由系统自动完成垃圾回收。Java开发中一直使?的就是垃圾回收技术。
人工引用计数 MRC(Manual Reference Count):内存的开辟和释放都由程序代码进?控制。相对垃圾回收来说,对内存的控制更加灵活,可以在需要释放的时候及时释放,对程序员的要求较高,程序员要熟悉内存管理的机制。
?动引用计数 ARC(Auto Reference Count):Xcode4.2及以上版本具有自动管理内存ARC机制,iOS 5.0的编译器特性,它允许用户只开辟空间,不用去释放空间。它不是垃圾回收。本质是MRC,只是编译器帮助程序员默认加了释放的代码。
iOS的内存管理
- iOS支持两种内存管理方式:ARC 和 MRC。
- MRC的内存管理机制是:引用计数。
- ARC基于MRC。
二、内存管理机制及影响引用计数的方法
引用计数
实际开发中,可能会遇到两个以上的指针使用同一块内存。C语言无法记录内存使用者的个数。Objective-C采用引用计数机制管理内存,当一个新的引用指向对象时,引用计数器就递增,当去掉一个引用时,引用计数就递减。当引用计数到零时,该对象就将释放占有的资源。
影响引?计数的方法
+ alloc:开辟内存空间,让被开辟的内存空间的引用计数变为1。这是由0到1的过程。
- retain:引?计数加1,如果内存空间之前引用计数为1,retain之后变为2,如果引用计数是5,retain之后变为6。
- copy:把某一内存区域的内容拷贝一份,拷贝到新的内存空间里去,被拷贝区域的引用计数不变,新的内存区域的引用计数为1。copy可以这样理解,如果指针A和指针B不想互相牵扯,A管理A的内存,B管理B的内存。
- release:引用计数减1,如果内存空间之前引用计数为4,release之后变为3,如果之前引用计数为1,release之后变为0,内存被系统回收。
- autorelease:未来的某一时刻(程序运行出自动释放池)引用计数减1。如果内存之前引用计数为4,autorelease之后仍然为4,未来某个时刻会变为3。
- dealloc:继承自父类的方法,当对象引用计数为0的时候,由对象自动调用。在dealloc方法中对变量的释放顺序与初始化的顺序相反,在最后调用[super init]。
autoreleasepool的使?
通过autoreleasepool控制autorelease对象的释放。
向一个对象发送autorelease消息,这个对象何时释放,取决于autoreleasepool。
NSAutoreleasePool *pool= [[NSAutoreleasePool alloc] init];
Person *p = [[Person alloc] init]; // retainCount为1
[p retain]; // retainCount为2
[p autorelease]; // retainCount为2 未来的某个时刻释放
[pool release]; // 此时 autorelease的对象引用计数-1
NSLog(@”%d”, [p retainCount]); // 打印结果为1
/* NSAutoreleasePool在ARC模式下运行,会得到编译错误,而不是运行错误。作为替代,@autoreleasepool{}被引入。本例只是实现简单效果。 */
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
和 [pool release];
就像一对括号,[xxx autorelease];必须写在两者之间。[xxx autorelease];出现在了两者之间,pool就会把接收autorelease的对象给保存起来(以栈的方式,把对象压入栈)当[pool release];的时候,pool会向之前保存的对象逐一发送release消息(对象出栈,越晚autorelease的对象,越早接收release消息)。
在iOS 5之后,不再推荐使用NSAutoreleasePool类,使用@autoreleasepool{}替代。之前写在NSAutoreleasePool *pool = [[NSAutoreleasePool
alloc] init]; 和 [pool release];
之间的代码,需要写在@autoreleasepool{}
的大括号里。出了大括号,自动释放池才向各个对象发送release消息。
三、内存管理的原则
引用计数的增加和减少相等,当引用计数降为0之后,不应该再使?这块内存空间。凡是使用了alloc、retain或者copy让内存的引用计数增加了,就需要使用release或者autorelease让内存的引?计数减少。在一段代码内,增加和减少的次数要相等。
四、copy
跟retain不同,一个对象想要copy,?成?己的副本,需要实现NSCopying协议,定义copy的细节(如何copy)。如果类没有接受NSCopying协议而给对象发送copy消息,会引起crash。
copy分为深copy和浅copy。我们经常使用(包括下面例子)的是浅copy,至于区别会在后续的博文中详细叙述。
copy?法的实现
Person.h?件
@interface Person : NSObject<NSCopying>
@property (nonatomic, retain) NSString *name;
@property (nonatomic, assign) int age;
@end
Person.m?件
@implementation Person
- (id)copyWithZone:(NSZone *)zone
{
Person *p = [[Person allocWithZone:zone] init];
p.age = self.age;
p.name = self.name;
return p;
}
main.m?件
Person *p = [[Person alloc] init];
p.name = @”张三”;
p.age = 20;
Person *p2 = [p copy];
// p2是p的副本。p2.name与p.name一样。p2.age与p.age一样。
版权声明:本文为博主原创文章,未经博主允许不得转载。