---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------
内存管理是程序设计中常见的资源管理的一部分。每个程序都会消耗内存。我们必须确保在需要的时候分配内存,在程序运行结束的时候释放占用的内存。如果只分配而不释放,就会发生内存泄漏:程序的内存占用量不断增加,最终会被耗尽而导致程序崩溃。同样需要注意的是,不要使用任何刚释放的内存,否则可能误用陈旧的数据,从而引发各种各样的错误。
对象生命周期
程序中的对象有生命周期。对象的生命周期包括诞生(通过alloc或new方法实现)、生存(接收消息并执行操作)、交友(通过复合以及方法传递参数)以及最终死去(被释放掉)。当生命周期结束时,它们的原材料(内存)将被回收以供新的对象调用。
引用计数
Cocoa采用了一种叫做引用计数(reference counting)的技术,也叫做保留计数(retain counting)。每个对象都有一个与之相关联的整数,被称作引用计数器或保留计数器。当某段代码需要访问一个对象时,该代码将该对象的引用计数器加一,表示“我要访问该对象”。当某段代码结束访问时,将对象的引用计数器减一,表示不再访问该对象。当引用计数器的值为0时,表示不再有代码访问该对象了。因此它将被销毁,其占用的内存被系统回收以便重用。
当使用alloc、new方法或者通过copy消息(接收到消息的对象创建一个自身的副本)创建一个对象时,对象的引用计数器值被设置为1。要增加对象的引用计数器的值,可以给对象发送一条retain消息。要减少,则可以发送一条release消息。
当一个对象因其引用计数器的值为0而即将被销毁时,Objective-C会自动向对象发送一条dealloc消息。你可以在自己的对象中重写dealloc方法,这样就能释放掉已经分配的全部相关资源。一定不要直接调用dealloc方法,OC会在需要的时候自动调用它。
要获得引用计数器当前的值,可以发送retainCount消息。retain、release和retainCount的方法声明如下:
- (id) retain;
- (oneway void) release;
- (NSUInteger) retainCount;
对象所有权
如果一个对象有指向其他对象的实例变量,则称该对象拥有这些对象。如果一个函数创建了一个对象。则称该函数拥有这个对象。
访问方法中的保留和释放
下面是一种合理的访问方法:
- (void) setEngine: (Engine *) newEngine
{
[newEngine retain];
[engine release];
[engine = newEngine];
}
自动释放
Cocoa中提供了一个自动释放池。NSObject提供了一个叫做autorelease的方法:
- (id) autorelease;
该方法预先设定了一条会在某个时间发送的release消息,其返回值是接收这条消息的对象。当给一个对象发送autorelease消息时,实际上是将对象添加到了自动释放池中。当自动释放池被销毁时,会向该池中的所有对象发送release的消息。
自动释放池
有两种方法可以创建一个自动释放池。
- 通过@autoreleasepool关键字
- 通过NSAutoreleasePool对象
Cocoa的内存管理规则
- 当使用new、alloc或copy方法创建一个对象时,该对象的引用计数器的值为1。当不再使用该对象时,你应该向该对象发送一条release或autorelease消息。这样,该对象将在其使用寿命结束时被销毁。
- 当通过其他方法获得一个对象时,假设该对象的引用计数器的值为1。而且已经被设置为自动释放,那么不需要执行任何操作来确保该对象得到清理。如果你打算在一段时间内拥有该对象,则需要保留它并确保在操作完成时释放他。
- 如果你保留了一个对象,就需要(最终)释放或自动释放该对象。必须保持retain方法和release方法的使用次数相等。
无论什么时候拥有一个对象,有两件事情必须清楚:怎么获得该对象的?打算拥有多长时间?
临时对象
在代码中使用某个对象,但是并未打算长期拥有该对象。如果是用new、alloc或copy方法获得的这个对象,就需要安排好该对象的内存释放,通常使用release消息来实现。
NSMutableArray *array;
array = [[NSMutableArray alloc] init]; // count 1
// use the array
[array release]; // count 0
如果使用其他方法获得一个对象,比如arrayWithCapacity:方法,则不需要关心如何销毁该对象。
NSMutableArray *array;
array = [NSMutableArray arrayWithCapacity: 17];
// count 1, autoreleased
// use the array
使用NSColor类对象的部分代码如下:
NSColor *color;
color = [NSColor blueColor];
// use the color
blueColor也不属于alloc、new、copy这三个方法,因此可以假设该对象的引用计数器的值为1并且已经被设置为自动释放。blueColor方法返回一个全局单例(singleton)对象---每个需要访问它的程序都可以共享的单一对象,这个对象永远不会被销毁。
拥有对象
通常,你可能希望在多段代码中一直拥有某个对象。典型的方法是,把它们加入到诸如NSArray或NSDictionary等集合中,作为其他对象的实例变量来使用。
如果使用了new、alloc或copy方法获得了一个对象,则不需要执行任何其他操作。该对象的引用计数器的值为1,因此它将一直存在着,你只需要确保在该对象的dealloc方法中释放它即可。
- (void) doStuff
{
// flonkArray is an instance variable
flonkArray = [NSMutableArray new]; // count 1
}
- (void) dealloc
{
[flonkArray release]; // count 0
[super dealloc];
}
如果是使用出new、alloc或copy以外的方法获得一个对象,需要记得保留该对象。如果编写的GUI程序,则需要考虑到事件循环。你需要保留自动释放的对象,以便这些对象在当前的事件循环结束后仍能存在。如,一个典型的图形应用程序往往花费许多时间等待用户操作。在控制程序运行的人慢腾腾做出决定(如用鼠标或按下某个键)以前,程序将一直处于空闲状态。当发生这样的事件时,程序被唤醒并开始工作,执行必要的操作以响应这一事件。在处理完这一事件后,程序返回到休眠状态并等待下一个事件发生。为了降低程序的内存空间占用,Cocoa会在程序开始处理事件之前创建一个自动释放池,并在处理结束后销毁。这样可以尽量减少累积的临时对象的数量。
当使用自动释放对象时,前面的方法可以按如下形式重写:
- (void) doStuff
{
// flonkArray is an instance variable
flonkArray = [NSMutableArray arrayWithCapacity: 17]; // count: 1 autoreleased
[flonkArray retain]; // count: 2 1 autoreleased
}
- (void) dealloc
{
[flonkArray release]; // count: 0
[super dealloc];
}
在当前事件循环结束(如果这是一个GUI程序)或自动释放池被销毁时,flonkArray对象接收到一条release消息,因而引用计数器的值从2减少到1。因为其计数器的值大于0,所以该对象将继续存在。因此,我们需要在dealloc方法中释放它,以便被清理掉。
自动引用计数
在Objective-C 中采用Automatic Reference Counting(ARC)机制,让编译器来进行内存管理。在新一代Apple LLVM 编译器中设置ARC 为有效状态,就无需再次键入retain 或者release 代码,这在降低程序崩溃、内存泄漏等风险的同时,很大程度上减少了开发程序的工作量。编译器完全清楚目标对象,并能立刻释放那些不再被使用的对象。如此一来,应用程序将具有可预测性,且能流畅运行,速度也将大幅提升。
如果想要在带埋中使用ARC,必须蛮做以下三个条件:
- 能够确定哪些对象需要进行内存管理
- 能够表明如何去管理对象
- 有可行的办法传递对象的所有权
第一个条件是对象的最上层集合知道如何去管理它的子对象。比如你有一个通过malloc:方法创建的字符串数组:
NSString **myString;
myString = malloc(10 * sizeof(NSString *));
这段代码创建了一个指向10个字符串的C型数组。因为C型数组不是可保留的对象,所以你无法在这个结构体里使用ARC特性。
第二个条件是你必须能够对某个对象的引用计数器的值进行加一或减一的操作。也就是说所有的NSObject类的子类都能进行内存管理。
第三个条件是在传递对象的时候,你的程序需要能够在调用者和接受者之间传递所有权。
关于Weak
当用指针指向某个对象时,你可以管理它的内存(通过retain和release),也可以不管理,如果你管理了,就拥有这个对象的强引用(strong reference)。如果你没有管理,那么你拥有的则是弱引用(weak reference)。比方说,对属性使用了assign特性。你便创建了一个弱引用。
为什么需要弱引用呢?因为它们有助于处理保留循环。假设你拥有一个由其他对象创建并且引用计数器的值为1的对象A。对象A创建了引用计数器值为1的对象B并将其作为子对象。对象B需要能够访问它的父对象。那么A的计数器则增加到2。当对象A的拥有者不再需要它的时候,就会向对象A发送release消息。这样A的计数器值被减少到1 。两个对象A和B的计数器值都不为0,都没有被释放掉,发生了内存泄漏。
为了解决这个问题,可以使用弱引用。使用assign来获取对象B指向A的引用。由于是弱引用,计数器的值不会增加。所以对象A的拥有者释放它的时候,它的计数器会变成0 ,对象B也会被释放。
这里很好的解决了这个问题,但是如果拥有3个对象,假设对象A通过强引用指向对象B,对象C通过弱引用指向B。当对象A释放了对象B,对象C仍指向B,但这个引用已经实效了,因为对象B已经被释放了。解决方法是让对象自己去清空弱引用的对象。这种特殊的弱引用,被称为归零弱引用(zeroing weak reference)。因为在指向的对象释放之后,这些弱引用就会被设置为零(nil)。
如果想要使用归零弱引用,必须明确的声明它阿门。有两种方式可以声明:声明变量时使用_weak关键字或对属性使用weak特性。
_weak NSString *myString;
@property(weak) NSString *myString;
使用ARC需要注意的两种命名规则:
- 属性名称不能以new开头,比如@property NSString *newString,这是不被允许的。
- 属性不能只有一个read-only而没有内存管理特性。默认的管理属性是assign。
---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------