码神之路:Object-C篇

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

2017-07-05  本文已影响7人  一只特立独行的道哥

最近觉得需要巩固一下基础知识,特地系统的回顾一下,内存管理篇是看<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 语言其他类型不同,其类型上必须附加所有权修饰符

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;

2.5 ARC 规则

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

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 的实现

释放谁都不持有的对象时,程序的动作如何?下面通过 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 资源. 良策是只在 需要避免循环引用时使用

上一篇 下一篇

猜你喜欢

热点阅读