iOS&OS X强大的内存管理总结

来源:互联网 发布:中国进出口数据分析 编辑:程序博客网 时间:2024/06/16 20:57

每一个iOS开发者,都需要理解引用计数这种内存管理方式,只有这样,才能处理好内存管理的问题。

1.内存管理/引用计数

1.1引用计数:

当生成对象的时候,对象的引用计数是1,当有一个新的指针指向这个对象的时候,引用计数+1.当这个指针不指向这个对象的时候,引用计数-1,如此,当引用计数为0时,销毁这个对象。引用计数的这种管理方式类似于Linux文件系统的硬链接,在Linux文件系统中,我们用**ln**命令可以创建一个硬链接(相当于retain),当删除一个文件时(相当于release),系统调用会检查文件的link count值,如果大于1,则不会回收文件所占有的磁盘区域,知道最后一次删除前,系统发现link count值为1,才会执行真正的删除操作。

1.2内存管理的思考方式

1. 自己生成的对象,自己所持有。    以*alloc  *new  *copy  *mutableCopy开头的方法名生成对象只有自己持有,但是对于allocate newer copying mutableCopyed方法则不成立以上规则。
    id obj = [[NSObject alloc] init];    //这里retainCount为1    NSLog(@"%ld",[obj retainCount]);
2. 非自己生成的对象,自己也能持有。    不是自己生成的对象,需要通过retain方法来持有对象。
    id obj = [NSMutableArray array];    [obj retain];
3. 不在需要自己所持有的对象时释放    不管是以alloc/new/copy/mutableCopy还是以retain方法持有的对象,当不再需要时,都需要调用release方法来释放。
    [obj release];
4. 非自己持有的对象无法释放   当自己不持有某个对象时,如果释放的话,就会导致程序崩溃。
    id obj = [NSMutableArray array];    [obj release];

1.3 苹果的实现方式

对于retainCount/retain/release这三个方法,_CFDoExternRefOperation函数按retainCount/retain/release操作进行分发,调用不同的函数。从调用的函数名可以猜出苹果的实现大概就是采用散列表来管理引用计数的(表键值为内存块地址的散列值)采用散列值的好处:1.  引用计数表各记录中存在内存块地址,可从各个记录中追溯到各对象的内存块。2. 如果出现故障导致对象占用的内存块损坏,但只要引用计数表没坏,就能够确认各内存块的地址。3. 在利用工具监测内存泄漏时,引用计数表的各个记录有助于监测各对象的持有者是否存在。

1.4 autorelease

NSAutoreleasePool对象的生存周期相当于c语言的自动变量的作用域,对于所有调用过release方法的对象,在废弃NSAutorelease对象时,都将调用release方法。
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];    id obj = [[NSObject alloc] init];    [obj autorelease];    [pool drain];
其实现应该是将对象加到pool的数组变量中,当调用drain时,将数组中全部的对象调用release方法。如果给NSAutoReleasePool对象调用autorelease方法会怎么样呢?会发生异常。
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];    id obj = [[NSObject alloc] init];    [obj autorelease];    [pool autorelease];


对于NSAutoreleasePool类,autorelease实例方法已经被重载,所以运行时会抛异常。

2.ARC

ARC 能够解决 iOS 开发中 90% 的内存管理问题

2.1 所有权修饰符

在Xcode4.2以后,默认设定对所有的文件ARC有效。ARC有效时,id类型和对象类型不同于c语言其他类型,类型上必须附加所有权修饰符。__strong__weak__unsafe_unretained__autoreleasing1.  __strong 修饰符id和对象类型在没有明确指定所有权修饰符的时候,默认为__strong修饰符
{    id __strong obj = [[NSObject alloc] init];}
附有__strong修饰符的变量obj在超出其变量作用域时,会释放其附有的对象。这点和c语言的自动变量类似。如果没有__strong的修饰符的MRC是?
{    id obj = [[NSObject alloc] init];    [obj release];}
虽然__strong修饰符能够解决retain,release问题,但是可能会导致循环引用问题。对象 A 和对象 B,相互引用了对方作为自己的成员变量,只有当自己销毁时,才会将成员变量的引用计数减 1。因为对象 A 的销毁依赖于对象 B 销毁,而对象 B 的销毁与依赖于对象 A 的销毁,这样就造成了我们称之为循环引用的问题,这两个对象即使在外界已经没有任何指针能够访问到它们了,它们也无法被释放。![图片来自《iOS开发进阶》](http://img.blog.csdn.net/20160804174033463)不只是两个对象会出现这种问题,多个对象也会出现循环引用的问题:![图片来自<<iOS开发进阶>>](http://img.blog.csdn.net/20160804175252311)有两种方法可以来解决这个循环引用。    1.  主动断开循环引用。    主动断开循环引用多出现于block相关代码逻辑中,比如说网络请求中,网络请求的回调时被持有的,如果回调中存在对ViewController的引用,就会存在循环引用的问题,因为:    - 1 ViewController持有网络请求对象,    - 2 网络请求对象持有block回调,    - 3 如果回调中存在self调用,那么就持有了ViewController,形成了循环引用。解决办法就是,就是主动断开循环引用,在网络请求结束之后,主动断开对block的持有,将其置空。不过,主动断开循环引用需要程序员显示的知道哪里需要断开引用,这又和手动来retain和release差不多啦。还有一种办法就是弱引用,弱引用就是要用到第二个关键字2. __weak    弱引用虽然持有对象,但是不增加对象的引用计数,这样就避免了循环引用,但是不能直接给对象符__weak关键字,因为是弱引用,所以一旦创建就会被释放,会有一个错误提示:Assigning retained object to weak variable; object will be released after assignment
NSMutableArray __weak *mutableArrayA = [[NSMutableArray alloc] init];
但是可以这样来消除循环引用:
    id __weak obj = nil;    NSMutableArray *mutableArrayA = [[NSMutableArray alloc] init];    obj = mutableArrayA;    NSMutableArray *mutableArrayB = [[NSMutableArray alloc] init];    [obj addObject:mutableArrayB];    [mutableArrayB addObject:obj];
利用xcode的Instruments 工具集可以很快地监测出项目的循环引用,在没有加入__weak关键字之前,在 Xcode 的菜单栏选择:Product -> Profile,然后选择 “Leaks”,再点击右下角的”Profile” 按钮开始检测。

这里写图片描述

这里会有一个图来展示是哪里出现的循环引用,当加入__weak关键字之后就不会出现循环引用问题啦。![这里写图片描述](http://img.blog.csdn.net/20160807155246596)最容易出现循环引用的地方就是delegate,两个viewController A和B,A需要弹出B让用户输入一些内容,B将内容返回给A,这时候A的delegate成员变量通常是一个弱引用,避免出现循环引用。![图片来自《iOS开发进阶》](http://img.blog.csdn.net/20160807155702652)系统对于每一个有弱引用的对象,都维护一个表来记录它所有的弱引用的指针地址。这样,当一个对象的引用计数为 0 时,系统就通过这张表,找到所有的弱引用指针,继而把它们都置成 nil。3. __unsafe__unretained 修饰符__unsafe__unretained是不安全的修饰符,在ARC中,符有ARC修饰符的变量的内存管理不输入编译器的工作。为什么有这个修饰符,__weak修饰符只能用于iOS5以及OX X Lion以上版本的应用程序,在iOS4及OX X Snow leopard只能用__unsafe__unretained修饰符.它与__weak修饰符的区别是,用它访问已经释放的对象,但应用程序只会在个别情况下会崩溃,所以在使用__unsafe__unretained的变量,有必要验证该变量是否存在。4. __autoreleasing    显示的用__autoreleasing和显示的用__strong一样稀奇,__autoreleasing的作用就是在ARC中加入像非ARC中autorelease的作用。
@autoreleasepool {        id __autoreleasing obj  = [[NSObject alloc] init];}
等同于非ARC
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];    id obj = [[NSObject alloc] init];    [obj autorelease];    [pool drain];
id类型的指针或对象指针是默认带有__autoreleasing修饰符的,但是不默认带__strong修饰符。例如:
    NSError *error = nil;    NSError **pError = &error;
这样赋值会有一个错误:Pointer to non-const type 'NSError *' with no explicit ownership类型不匹配。添加上__strong修饰符就没有编译错误。
    NSError *error = nil;    NSError *__strong *pError = &error;
同理__weak修饰符亦是如此。在不能使用__strong修饰符和__weak修饰符的c++用智能指针std::shared_ptr和std::weak_ptr对应代替。

2.2 ARC规则

1.  不能使用retain/release/retainCount/autorelease。2.  不能使用NSAllocateObject/NSDeallocateObject。3.  须遵守内存管理的方法命名规则。   在非ARC时,以alloc new copy mutableCopy为名称开始的方法,必须返回给调用方所持有的对象,在ARC中一样适用,但是在ARC中要增加一条规则-init以init开始的方法必须是实例方法,并且必须返回对象,返回的对象应为id类型或该方法声明类的对象类型,亦或是该类的超类,子类型,该返回对象并不注册到autoreleasepool里面,基本上只是对alloc方法返回值的对象进行初始化。4.  不要显式调用dealloc。5.  使用@autoreleasepool块替代NSAutoreleasePool.6.  不能使用区域(NSZone)7.  对象型变量不能作为c语言结构体(struct/union)的成员c语言的结构体成员中,如果存在OC对象型变量,就会报错,因为c语言的规约上没有方法来管理结构体成员的生存周期。要把对象型变量加到结构体成员中,可强制转换为void *或是附加__unsafe_unretained修饰符。8.  显示转换id和void *void *转换为id类型,需要__bridge__transfer,意思是被转换的变量所持有的对象在该变量被赋值给转换变量后随之释放。
    void *p = 0;    id obj = (__bridge_transfer id)p;

在非ARC中,

    void *p = 0;    id obj = (id)p;    [obj retain];    [(id)p release];
id类型转换为void *类型,会用到__bridge_retained,意思是可使要转换赋值的变量也持有所赋值的对象。
    id obj = [[NSObject alloc] init];    void *p = (__bridge_retained void *)obj;

在非ARC中:

    id obj = [[NSObject alloc] init];    void *p = obj;    [(id)p retain];
9.  Core Foundation的内存管理底层的Core Foundation对象,在创建时大多以XxxCreateWithXxx这样的方式创建,eg:CFStringRef str = CFStringCreateWithCString(kCFAllocatorDefault,”hello,world”,kCFStringEncodingUTF8);对于这些对象的引用计数的修改,要使用CFRetain 和 CFRelease  除此之外,在ARC下,我们有时需要将一个Core Foundation对象转换成一个Objective-C对象,这个时候告诉编译器,转换过程的引用计数需要做如何的调整,这个过程和void *与id转换类似。

2.3 属性声明的属性对应的所有权修饰符

assign -> __unsafe_unretained修饰符copy   -> __strong修饰符(但是赋值的是被复制的对象)retain -> __strong修饰符strong -> __strong修饰符__unsafe_unretained —> __unsafe_unretained修饰符weak   -> __weak修饰符在 ARC 的帮助下,iOS 开发者的内存管理工作已经被大大减轻,但是我们仍然需要理解引用计数这种内存管理方式的优点和常见问题,特别要注意解决循环引用问题。
0 0
原创粉丝点击