内存管理篇之自动引用计数

最近觉得需要巩固一下基础知识,特地系统的回顾一下,内存管理篇是看<Objective-C 高级编程>所做的笔记(其实就是摘抄)

一.MRC

1 . 使用 alloc,copy,new,mutableCopy方法可以生成并持有对象,或者使用 retain 持有对象,一旦不需要,务必立即使用 release释放

无法释放非自己持有的对象

2 . 使用非 “alloc,copy,new,mutableCopy,retain" 中所示的方法获取的对象, 自己并不是持有者, 所以不能使用 release 释放, 例如

-(id)object{
    //自己持有对象
    id obj = [NSObjct alloc] init];
    //取得的对象存在,但自己不持有: 加入 autoreleasePool, 使得对象在超出指定的生存范围时能够自动并正确的释放
    [obj autorelease];
    return obj;
}

//取得的对象存在,但自己不持有
id obj1 = [obj0 object];

//当然也可以通过 retain 来持有
[obj1 retain];

//取得的对象存在,但自己不持有
id obj2 = [obj0 object];

//此时
[obj1 release]; //正确,不会崩溃

[obj2 release]; //错误,会引起崩溃,因为 obj2并不持有对象
    

3 . 引用计数

GUNstep 将引用计数保存在对象占用内存块头部的变量中

  1. 少量代码即可完成
  2. 能够统一管理引用计数用内存块与对象用内存块

苹果 将引用计数保存在引用计数表的记录中

  1. 对象用 内存块的分配无需考虑内存块头部
  2. 引用计数表个记录中存有内存块地址,可从各个记录追溯到各对象的内存块(�即使出现故障导致对象占用的内存块损坏,但只要引用计数表没有被破坏,就能够确认内存块的位置)

另外, 在利用工具检测内存泄漏时,引用计数表的各记录也有助于 检测各对象的持有者是否存在

3.1 引用计数式内存管理的思考方式

  • 自己生成的对象,自己所持有
  • 非自己生成的对象,自己也能持有
  • 不在需要自己持有的对象时释放
  • 非自己持有的对象无法释放

4 . autorelease

  1. 当超出作用域时,对象实例的 release 实例方法会被调用.

  2. 在大量产生 autorelease 对象时,只要不废弃pool 对象,生成的对象就不能被释放,因此会造成内存不足的现象,典型的例子就是读入大量的图像并改变它们的尺寸(for循环中)

    for(int i = 0;i < image_count;i++){
        /*
         *  读入图像
         * 大量产生 autorease
         * 由于没有废弃 NSAutoreleasePool 对象
         * 最终导致内存不足
         */
    }
    
    

    在此情况下,有必要在适当的地方生成,持有或废弃NSAutoreleasePool 对象

    for(int i = 0;i < image_count;i++){
        NSAutoreleasePool *pool = [NSAutoreleasePool new];
        /*
         *  读入图像
         * 大量产生 autorease
         */
         
         [pool drain];
         
        /*
         * 通过 drain,autorelease 的对象被遗弃 release
         */
    }
        
    

    3 .通常在 object-c中,也就是 Foundation 框架中,无论调用哪一个对象的 autorelease 实例方法实现上都是调用 NSObject 类的autorelease 实例方法.但是对于 NSAutoreleasePool类, autorelease 实例方法已经被该类重载, 因此运行时会出错

二. ARC

1. 设置 ARC 有效的编译方法如下:

- 使用 clang(LLVM编译器)3.0或以上版本
- 指定编译器属性为 "-fobjc-arc"

2. 所有权修饰符

ARC 有效时, id 类型和对象类型 通 C 语言其他类型不同,其类型上必须附加所有权修饰符

  • __strong
  • __weak
  • __unsafe_unretained
  • _autoreleasing

2.1 __strong 修饰符

是 id 类型和对象类型默认的所有权修饰符

以下源代码中的 id 变量,实际上被附加了修饰符

id obj = [NSObject new];

id __strong obj = [NSObject new];

相同.(默认为__ strong)

内存泄漏:

__strong 引起的循环引用导致内存泄漏.

所谓的泄漏就是应当废弃的对象在超出其生存周期后继续存在

循环引用的两种方式:
1. 两个对象相互强引用
    {
        id test0 = [NSOject new];
        id test1 = [NSOject new];
        
        [test0 setObject:test1];
        [test1 setObject:test0];
    }   
2. 自己持有自己 
    {
        id test0 = [NSOject new];
        [test0 setObject:test0];
    }

2.2 __weak 修饰符

__weak可以避免循环引用, weak 与 strong 相反,提供弱引用.弱引用不能持有对象实例.

__weak持有某个对象的弱引用时,若该对象释放,则该弱引用自动失效,且被设置为 nil 状态

访问附有__ weak 修饰的变量时必须访问注册到 autoreleasepool 的对象
因为__ weak 修饰的变量只持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被释放,如果把要访问的对象注册到 autoreleasepool 中,那么在@ autorepool 块结束之前都能确保该对象存在.

2.3 __unsafe __unretained 修饰符

  • iOS4和 OS X Snow Leopard 中用来替代__ weak 修饰符
  • 不安全的所有权修饰符, 附有该修饰符的变量 不属于编译器的内存管理对象
  • 附有该修饰符的变量和附有__ weak 一样,不能持有对象的强引用,但也不持有弱引用
  • 使用该修饰符时,赋值给附有__ strong修饰符的变量必须要确保被赋值的对象确实确实存在,否则程序就会崩溃

2.4 __autoreleasing 修饰符

ARC 有效时 autorelease方法不能使用,但是有效

//ARC 无效时:
NSAutoreleasePool *pool = [NSAutoreleasePool new];
id obj = [NSObject new];
[obj autorelease];
[pool drain];

等同于

//ARC 有效时:
@autoreleasepool{
    id __autoreleasing obj = [NSObject new];
}
  • id的指针或者对象的指针在没有显示指定时会被附加上__autoreleasing修饰符,例如,由id *obj推出的是``` id __autoreleasing * obj
  • 使用附有__autoreleasing的变量作为对象取得参数,与除alloc/copy/new/mutableCopy 外其他方法返回值取得对象完全一样,都会注册到 autoreleasepool 中,并取得非自己生成并持有的对象
NSError *error = nil;
BOOL result = [obj performOperationWithError:&error];

performOperationWithError 的声明为:

-(BOOL) performOperationWithError:(NSError **)error;
注意:
  • 赋值给对象指针时,所有权修饰符必须一致
    错误范例�:这段代码会产生编译错误
NSError *error = nil; //==>NSError  __strong*error = nil;
NSError **pe = &error; //NSError * __autoreleasing*pe = &error;

此时,对象指针必须附加 __strong修饰符

NSError *error = nil;
NSError * __strong*pe = &error;
  • 无论 ARC 是否有效,调试用的非公开函数 _objc_autoreleasePoolPrint()都可以使用.利用这一函数可以有效的帮助我们调试注册到 autoreleasepool 上的对象

2.5 ARC 规则

在 ARC 有效时编译,必须遵守以下规则:

  • 不能使用retain.release/retainCount/autorelease

  • 不能使用 NSAllocateObject/NSDeallocateObject

  • 必须遵守内存管理的方法命名规则

    • 除了 在ARC无效时 alloc/copy/new/mutableCopy 的命名规则外,在 ARC 有效时要追加一条命名规则: init
    • 以 init开始的方法,必须为实例方法,并且必须返回对象.返回的对象因为 id 类型或该方法声明类的对象类型,亦或是该类的超类型子类型. 该返回对象不注册到 autoreleasepool 上,基本上只是对 alloc 方法返回值的对象进行初始化处理,并返回该对象
  • 不要显示调用 dealloc

  • 使用@ autorelease 块替代 NSAutoreleasePool

  • 不能使用区域(NSZone)

  • 对象型变量不能作为 C 语言结构体(struct/uinion)的成员

  • 显示转换 id 和 void *

    • 在 ARC 无效时 id 变量强制转换成 void*变量是没有问题的,甚至调用其实例方法都没问题
    • 但在 ARC 有效时会引发编译错误,id 型或者对象型变量赋值给 void* 或者逆向赋值时都需要进行特定的转换.
    • 如果只想单纯的赋值可以使用 __bridge 转换,如
    id obj = [NSObject new];
    void *p = (__bridge void *)obj;
        
    
    • 但这样的转换其安全性与赋值给 __unsafe_unretained 修饰符相近,甚至会更低. 稍有不注意,就会因为野指针而导致程序崩溃
    • __bridge转换中有另外两种转换:
    • __bridge_retained 转换,可以使得要转换赋值的变量也持有所赋值的对象,就是与 retain 类似
    • __bridge_transfer 转换, 与 __bridge_retained相反,被转换的变量所持有的对象在改变量被赋值给转换目标变量后随之释放, 就是与 release 类似
     id obj = (__bridge_transfer void *)p;
     在 ARC 无效时,等同于如下代码:
     id obj = (id)p;
     [obj retain];
     [(id)p release];
     
    

2.6 ARC 的实现

在 ARC 有效时不能使用 release 方法,但编译器会自动在合适的位置插入 release. 下面看一下非 alloc/new/copy/mutableCopy 方法的情况

{
    id obj = [NSMutableArray array];
}

编译后的代码是:

id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutoreleaseReturnValue(obj);
objc_release(obj);

objc_ retainAutoreleaseReturnValue函数主要用于最优化程序运行. 他是用于自己持有(retain)对象的函数,但他持有的对象应为返回注册到 autoreleasepool 中对象的方法,或是函数的返回值.且这个函数跟 objc_autoreleaseReturnValue. 它用于alloc/new/copy/mutableCopy方法以外的 类似 NSMutableArray的 array 类方法等返回对象的实现上.

看一看 array 通过编译器进行的转换

+(id)array{
    return [[NSMutableArray alloc] init];
}

经编译,转换后:

//模拟编译代码;
+(id) array{
    id obj = objc_msgSend(NSMutableArray,@selector(alloc));
    objc_msgSend(obj,@selector(init));
    objc_autoreleaseReturnValue(obj);
}
  • objc_autoreleaseReturnValue 函数会检查使用该函数的方法或函数调用方的执行命令列表, 如果方法或函数的调用方在调用了方法或函数后紧接着调用objc_retainAutoreleaseReturnValue函数,那么 就不将返回的对象注册到 autoreleasepool 中,而是直接传递到方法或者函数的对用方.
  • objc_retainAutoreleaseReturnValue 与 objc_retain 不同,它即使不注册到 autoreleasepool 中而返回对象,也能够正确获取对象.

2.6.1 __weak 的实现

  • 若附有__weak修饰符的变量所引用的对象被废弃,则将 nil 赋值给该变量
  • 使用附有_weak修饰符的变量,即是 使用注册到 autoreleasepool 中的对象,即每次使用的时候都会注册到autoreleasepool一次,对策是,把这个__weak修饰的变量再赋值给附有__strong 修饰的变量 tmp,然后再使用tmp

释放谁都不持有的对象时,程序的动作如何?下面通过 objc_release 函数释放跟踪:

  1. objc_release
  2. 因为引用计数为0所以执行 dealloc
  3. _objc�_dispose
  4. objc_destructInstance
  5. objc_clear_deallocating

对象被释放后调用的objc_clear_deallocating函数动作:

  1. 从 weak 表中获取废弃对象的地址为 键值的 记录
  2. 将包含在记录中的所有附有 __weak 修饰符变量的地址,赋值为 nil
  3. 从 weak 表中删除该记录
  4. 从引用计数表中删除 释放的对象的地址为键值的记录

根据以上步骤,如果附有__weak 修饰符的变量所引用的对象被释放,则将 nil 赋值给该变量这一流程就被实现,由此可知,如果大量使用附有__weak 修饰符的变量,则会消耗响应的 CPU 资源. 良策是只在 需要避免循环引用时使用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容