码迷,mamicode.com
首页 > 其他好文 > 详细

11.内存管理

时间:2015-11-18 10:42:50      阅读:235      评论:0      收藏:0      [点我收藏+]

标签:

原文:http://rypress.com/tutorials/objective-c/memory-management

内存管理

正如在属性章节中讨论的那样,任何的内存管理系统的目的就是要通过控制对象的生命周期去减少内存的占用。IOS 和OS X应用通过 object-ownership, object-ownership的意思就是确保在你需要的时候你所有拥有的对象始终存在。

 object-ownership是通过引用计数系统来实现的,记录每一个对象当前被多少其他对象所拥有。当你声明对于某个对象拥有的时候,其实你就增加了它的引用计数,当你声明不再需要某对象的时候则相应的在引用计数系统中减少了它的一个计数。让这个计数大于零的时候说明此对象需要确保存在,当某个对象的引用计数等于零的时候则说明无人需要它,此对象可以被销毁。

技术分享

Destroying an object with zero references

在过去,开发者需要通过在NSObject protocol中定义的内存管理方法来手动的控制对象的引用计数。这被称之为Manual Retain Release (MRR)。然而,Xcode 4.2中引入了Automatic Reference Counting (ARC)功能,它可以自动的为你做这些事情。现代应用应该始终开启ARC,因为ARC非常可靠可以使得开发者不用太关心内存管理的细节将注意力集中于应用功能本身。

在这章节中将通过MRR来解释主要的引用计数的概念,和讨论ARC在实际使用中的注意事项。

Manual Retain Release

在Manual Retain Release环境中,你需要负责所有的对象的ownership。你需要通过如下定义的内存管理方法来实施这个操作。

MethodBehavior
alloc Create an object and claim ownership of it.
retain Claim ownership of an existing object.
copy Copy an object and claim ownership of it.
release Relinquish ownership of an object and destroy it immediately.
autorelease Relinquish ownership of an object but defer its destruction.

手动管理对象的ownership似乎是一个令人畏惧的任务,但是事实上也是非常简单的。你只需要当需要某个对象的时候声明需要它在不需要这个对象的时候释放这个ownership即可。实际操作中对于某个对象去平衡【allocretain,  copy】和【release 或者 autorelease】的操作。

当你忘记了这种平衡操作,那么就会发生两间不好的事情。如果你忘记去释放某个对象,它的内存将得不到释放导致内存泄露。小的内存泄露影响不大,但是如果内存泄露到是足够多的时候你的程序可能就会奔溃。另一方面如果你对于某个对象释放太多次,你可能会得到一个dangling pointer(xf-xrh-xf译注:平时说的野指针),当你去当问这个指针对应的内存的时候你的程序也将会有奔溃的风险。

技术分享

Balancing ownership claims with reliquishes

接下来的小节中将解释如何使用上述介绍过的方法去防止内存泄露和也指针。

启用MRR

在xcode中我们需要先将Automatic Reference Counting功能关闭,才能启用MRR。在功能导航窗口中点击工程图标,选择Build Setting tab,搜索automatic reference counting项目,将此项有Yes改为No。

技术分享

Turning off Automatic Reference Counting

记住,我们只是由于教学需要才这么做,在现实中你可千万不要这么做哦。

alloc 方法

alloc方法我们在本教程中已经多次使用过了,但是记住它不仅仅为某个对象申请了内存同时也将这个对象的引用计数加1(即为1).

// main.m
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray *inventory = [[NSMutableArray alloc] init];
        [inventory addObject:@"Honda Civic"];
        NSLog(@"%@", inventory);
    }
    return 0;
}

上述代码中是不是看上去很眼熟。所有的逻辑就是创建了一个 mutable array,增加一个值,现实这个值。从内存管理的角度看,我们现在拥有了inventory对象,一旦拥有我们也就有责任去释放它。

但是从代码上看我们并没有才去释放它的操作,我们的程序目前就存在内存泄露的情况。我们可以在xcode中通过Product > Analyze 或者快捷键盘 Shift+Cmd+B去打开内存分析工具,一般会出现如下警告。 

技术分享

A memory leak! Oh no!

这只是一个很小的对象,所以造成的泄露不足以致命。然而,如果在某个长的循环中不断出现则程序将out of memory 并且崩溃。

release 方法

release方法接触对某个对象的拥有并将此对象的引用对象数减1.所以你可以在上述代码的NSLog()语句之后使用它来避免内存泄露。

[inventory release];

现在alloc和release已经缺德平衡了,静态内存分析工具不会在提示刚才的错误。通常地,我们需要在同一个方法中创建和销毁对象,正如我们现在做的。

如果我们对于某个对象释放的太快,这将造成野指针。例如我们可以尝试在 NSLog()之前去释放这个对象,那么在 NSLog()语句中还依然去通过inventory变量访问这段内存程序就很可能奔溃:

技术分享

Trying to access an invalid memory address

所以重点是,确定不再使用某个对象了在去释放它。

retain 方法

retain方法是对于一个已经存在的对象做拥有声明。就好像对操作系统说“嘿,我也需要这个对象,请不要提前释放它”,这在对象属性中确保所使用的对象保持存在时候非常重要。

举个例子,我们使用retain去创建一个指向inventory数组的strong reference。创建一个新的CarStore类并且按照下面的代码处理:

// CarStore.h
#import <Foundation/Foundation.h>

@interface CarStore : NSObject

- (NSMutableArray *)inventory;
- (void)setInventory:(NSMutableArray *)newInventory;

@end

手动的声明inventory属性的访问方法。第一个迭代的CarStore.m中只是提供简单的访问方法的实现:

// CarStore.m
#import "CarStore.h"

@implementation CarStore {
    NSMutableArray *_inventory;
}

- (NSMutableArray *)inventory {
    return _inventory;
}

- (void)setInventory:(NSMutableArray *)newInventory {
    _inventory = newInventory;
}

@end

 

 

回到main.m文件,我们将inventory变量赋值给CarStore的属性:

// main.m
#import <Foundation/Foundation.h>
#import "CarStore.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray *inventory = [[NSMutableArray alloc] init];
        [inventory addObject:@"Honda Civic"];
        
        CarStore *superstore = [[CarStore alloc] init];
        [superstore setInventory:inventory];
        [inventory release];

        // Do some other stuff...

        // Try to access the property later on (error!)
        NSLog(@"%@", [superstore inventory]);
    }
    return 0;
}

这个inventory属性在最后一行输出语句中其实已经变成野指针了。现在superstore对象对于相应的属性只具有weak reference。为了获得strong reference的效果我们需要在 setInventory:方法中做如下处理:

// CarStore.m
- (void)setInventory:(NSMutableArray *)newInventory {
    _inventory = [newInventory retain];
}

这样就保证了在superstore对象使用的时候inventory依然存在。注意retain方法返回对象本身。

不幸的是,这样做又会导致另外的问题:retain方法使用打破了和release方法的平衡性,所以其实我们还是存在内存泄露。当通过setInventory:方法给属性设定另外的对象的时候劳的对象将无法被释放。所以在setInventory:方法中需要对老的对象进行release操作。

// CarStore.m
- (void)setInventory:(NSMutableArray *)newInventory {
    if (_inventory == newInventory) {
        return;
    }
    NSMutableArray *oldValue = _inventory;
    _inventory = [newInventory retain];
    [oldValue release];
}

以上的代码就是属性参数retain 和strong的原理。很显然直接使用属性参数比这样做要方便很多。

技术分享

Memory management calls on the inventory object

上图就是我们实验的inventory变量的整个生命周期了,上图可以看到alloc和retain与release方法的平衡关系。

copy 方法

有时候可用copy方法来替代retain方法,copy方法创建一个全新的对象,并在这个对象引用计数+1,而对于被拷贝的对象不会有任何影响。所以如果你像拷贝inventory数组而不是直接引用的话,你可以将setInventory:方法修改如下:

// CarStore.m
- (void)setInventory:(NSMutableArray *)newInventory {
    if (_inventory == newInventory) {
        return;
    }
    NSMutableArray *oldValue = _inventory;
    _inventory = [newInventory copy];
    [oldValue release];
}

你或许还记得之前章节中讲到的copy属性参数,copy过来的值爆出copy时候的值,这一点一定要注意。

autorelease 方法

不像release,autorelease方法也同样释放一个对象的ownership,但是它不会立即就销毁对象,而是延迟到程序之后才完成。这样就使得你在对象真正被销毁之前有机会去坐一些其他事情。

例如,考虑下面的工厂方法,这个方法创建并返回一个CarStore对象:

// CarStore.h
+ (CarStore *)carStore;

计数上来说,carStore方法有责任去release对象,因为调用者没有办法知道它拥有被被返回的对象。所以在此种方法中需要返回一个autorelease对象,像这样:

// CarStore.m
+ (CarStore *)carStore {
    CarStore *newStore = [[CarStore alloc] init];
    return [newStore autorelease];
}

释放对象的操作在对象创建后马上就调用了,但是这个对象依然会在调用者访问的时候保持存在,他会在@autoreleasepool{}块的最后被释放掉,释放的方法是内部调用了release方法。这就是为什么@autoreleasepool{}今天在main方法中出现,它就是保证了所有autirelease对象能够被release掉。

所有的内置工厂方法比如NSString的stringWithFormat:方法和stringWithContentsOfFile:方法等的内部机制都如同刚才介绍的carStore方法。在ARC之前,这种方式也是一种便利的措施。

如果你改变了superstore的从alloc/init的构造形式转换成如下形式,那么一定要注意不要去主动释放,因为这不是你干的活。

// main.m
CarStore *superstore = [CarStore carStore];

事实上,你不被允许去释放superstore实例因为你不是真正的拥有者-事实上carStore的工厂方法才能这么做。避免去显式的release 已经标记为autorelease的对象是相当重要的,否则将会得到野指针造成程序的奔溃。

dealloc 方法

一个对象的dealloc方法是init方法的一个对立面。在对象被摧毁前此方法会被调用,使得你能够有机会做一些清理动作。这个方法会被自动调起来,你千万不要去手动的调用它。

在MRR环境中,在dealloc方法中最长干的事情就是释放对象内部属性变量。假设当我们要释放Carstore对象的时候在dealloc阶段应该做什么呢?那就是去释放_inventory变量,错过了早dealloc方法中机会那么就没有办法释放他了,就会造成内存泄露。

// CarStore.m
- (void)dealloc {
    [_inventory release];
    [super dealloc];
}

注意到在dealloc方法的最后我们将调用父类的dealloc方法以确保在父类中的变量及时得到释放,一般的规则是尽量的保持dealloc方法的简单,不要在里面做任何与你业务相关的逻辑代码在里面。

MRR 总结

手动内存管理说来也简单。关键在于保持alloc,retain copy和release,autorelease的平衡性,否则将会出现野指针或者内存泄露的风险。

记住这个小节中只是介绍MRR的目的在于去了解IOS和OSx内存的管理方式。在现实中MRR已经过时了,你可能在老的文档中能够看到对他们使用方法的描述。了解了MRR使得我们ARC的了解打好了基础。

自动引用计数ARC

现在你了解了MRR,但是你可以全部忘记这些。ARC的内部原理其实和MRR一致,但是它帮你自动地去管理内存操作。这个对OC开发人员来说是相当利好的,这样就可以聚焦应用本身而无需为繁琐复杂的内存控制分散注意力。

ARC可以减少内存管理人为上的错误,唯一不适用ARC的场景就是你可能在维护一个就代码。接下来的小节中将介绍主要的MRR和ARC之间的重大区别。

 

启用 ARC

首先你要启用ARC,还记得在前面我们是怎么关闭它的吗?相反操作即可。

技术分享

Turning on Automatic Reference Counting

不再有任何的内存管理方法

ARC会自动的分析你的代码管理你的对象,然后在核实的机会去自动的调用retain和release等方法。所以ARC将全面掌控内存管理的职责,在代码中绝对不要去调用retainrelease, or autorelease方法。

新的属性参数

ARC引入了一些属性的参数。你可以使用strong属性参数来替换retain方法,使用weak替换assign方法。所有的其他参数已经在Properties张杰讨论过了。

dealloc 方法 在 ARC

deallocation在ARC中也比较特殊,你不需要像之前在The dealloc Method提到的那样去释放内部的变量,ARC会帮你处理。事实上父类的dealloc也会自动被处理,所以你完全不用去关心deallocation。

大多数的时候,这意味着你不需要去增加定制的dealloc方法,但是当你在oc中使用更加地级别的malloc方法申请内存的时候你有必要在dealloc方法中调用free()方法去手动释放。

总结

ARC使得开发者几乎忘记了内存管理的存在,让开发者专注于应用本身。对于开发者来说唯一需要关心就是在Properties章节提到的属性参数的使用,

当目前为止你应该了解了OC中应该了解的内容。唯一剩下的事情就是数据类型的讲解,下一个章节中我们将介绍OC的标准类型,从字符串,数组,字典甚至是日期类型。

11.内存管理

标签:

原文地址:http://www.cnblogs.com/ios123/p/4940159.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!