您的位置:首页 > 移动开发 > Objective-C

Objective-C学习笔记_内存管理(一)

2015-08-04 12:58 561 查看

一、内存管理的⽅式

大家都去过图书馆,而图书馆里的书是可以借出的。我们来设想这样一个场景,大家都去借书,但是从来没有人去还书,那么最后,这个图书馆会因为无书可借而倒闭,每个人都没法再使用图书馆。计算机也是这样,当程序运行结束时,操作系统将回收其占用的资源。但是,只要程序运行就会占用资源,如果不进行清理已经不用的资源,资源最终将被耗尽,程序将崩溃。

学会内存管理我们就明白什么时候由你释放对象,什么时候你不能释放。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一样。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: