美文网首页码神之路:Object-C篇
内存管理篇之自动引用计数

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

作者: 一只特立独行的道哥 | 来源:发表于2017-07-05 11:13 被阅读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 语言其他类型不同,其类型上必须附加所有权修饰符

    • __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 资源. 良策是只在 需要避免循环引用时使用

    相关文章

      网友评论

      本文标题:内存管理篇之自动引用计数

      本文链接:https://www.haomeiwen.com/subject/nvafhxtx.html