美文网首页iOS OC 学习手册
引用计数内存管理和 ARC 那点事

引用计数内存管理和 ARC 那点事

作者: 47号同学 | 来源:发表于2016-09-27 10:58 被阅读239次
    .png

    前言

    程序员在开发程序的时候,对内存的管理是很重要的一件事情,比如如何高效使用内存,防止内存泄露,降低内存的峰值等。如何很好去处理这些问题的前提就必须对内存的管理机制有一定的掌握了,本文只要简单介绍引用计数式内存管理方式,和iOS5推出 ARC,引用计数内存管理方式是如何应用在 ARC 上的。从 MRC 到 ARC ,让程序员关注于更有趣的代码。

    内存管理的思考方式:

    1.自己生成的对象,自己所持有

    (1)alloc / new / copy / mutableCopy 等方法。

    (2)alloc / new / copy / mutableCopy 使用该单词开头,命名是以驼峰的方式的办法,即是该办法生成并持有对象。

    eg:

    • allocMyObject; (✅)
    • newThatObject; (✅)
    • copyobject; (❌)

    2.非自己生成的对象,自己也能持有

    retain 方法

    3.不在需要自己持有的对象时释放

    release 方法 .如果释放不是自己持有的对象,则程序会崩溃。

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

    ps:id objArray = [NSArray array]; 类似调用类办法时,生成对象,但并不持有,又是怎么实现的呢?

    创建对象的代码如下:

    -(instancetype)object
    {
        id object = [[NSObject alloc]init];
        [object autorelease];//防止传递的时候,object已经被释放了。加了 autorelease ,使对象在超出作用域时能够自动并正确释放(调用 release 办法)
        NSLog(@"%@",object);
        return object;
    }
    

    alloc / retain / release / dealloc 实现

    OS X,iOS 中大部分的源码没有公开,但是 GNUstep 是 Cocoa 框架的互换框架。也就是说,能从 GNUstep 中窥探苹果的实现。

    alloc:

    + (id)alloc;
    + (id) allocWithZone:(NSZone *)z;
    ...
    //具体代码就不贴了 大概讲述一下实现思路就可以。
     通过分配对象的所需的内存大小,所分配的区域,和通过赋值对对象内存头部的retained +1;
    

    为什么要通过区域来创建对象呢?

    • 它是为了防止内存碎片化而引入的结构。对内存分配的区域本身进行多重化管理,根据使用对象的目的,对象的大小分配内存,从而提高了内存管理的效率。

    retain

    由对象寻址找到对象内存头部,从而访问其中的 retained 变量 —> +1。

    release

    由对象寻址找到对象内存头部,从而访问其中的 retained 变量.当retained 变量大于0时减1,等于0时调用 dealloc 实例办法,废弃对象。

    值得一提的是:

    当对象在执行最后一次 release 时,系统知道马上要回收该对象了,所以并不会对对象进行 retainCount 减1了,因为不管减不减1,这个对象的内存都是要被回收的,他所在的内存区域,包括 retainCount 也是没有意义的了。不将 retainCount 从1减到0,这样会减少一次内存操作,加速对对象的回收。

    具体代码演示可以见下方总结一下之记住。

    dealloc

    找到对象内存地址,直接 free()。

    总结一下:

    • 在 Objective-C 的对象中存有引用计数这一整数值
    • 调用 alloc 或是 retain 方法后,引用计数值加1
    • 调用 release 方法后,引用计数值减1
    • 引用计数值为0时,调用 dealloc 方法废弃对象

    记住:当对象被释放掉的时候,该对象之前所占有的内存已经被收回。但是被收回的内存不一定马上被复用(暂时没有被复用的对象,成为了悬挂指针)。所以有时候我们向已经释放的对象发送消息,会收到不在预期中的效果。如果收回的对象内存被复用,则程序会崩溃。故不应该向已经释放的对象发送消息,会得到无法预期的结果。

    -(void)testARC
    {
        id objAlloc = [[NSObject alloc]init];
        NSLog(@"allco生成并自我持有:%lu",[objAlloc retainCount]);
        [objAlloc release];
        NSLog(@"allco生成并自我持有 release:%lu",[objAlloc retainCount]);//不应该向释放的对象发送消息,已经被回收的内存无法预期其是否已经被复用
     }
    
    2016-09-21 09:50:57.796 ARCByCrudherWu[1325:32457] allco生成并自我持有:1
    2016-09-21 09:50:57.797 ARCByCrudherWu[1325:32457] allco生成并自我持有 release:1
    

    简述苹果实现

    retainCount / retain / release

    int _CFDoExternRefOperation(uintptr_t op, id obj)  
    {  
      CFBasicHashRef table = 取得对象的散列表(obj);  
      int count;  
      
      switch(op) {  
      case OPERATION_retainCount;  
        count = CFBasicHashGetCountOfKey(table, obj);  
        return count;  
      case OPERATION_retain:  
        CFBasicHashAddValue(table, obj);  
        return obj;  
      case OPERATION_release:  
        count = CFBasicHashRemoveValue(table, obj);  
        return 0 == count;  
      }  
    } 
    

    苹果根据不同操作去调用不同的函数实现。从该函数中可以看出,苹果大概采用的是散列表(引用计数表)的形式。CNUstep 将引用计数保存在对象占用内存块的头部,而苹果是保存在引用计数表的记录中。

    通过内存块头部管理引用计数的好处:

    • 少量的代码就可以实现
    • 能够一起管理引用计数的内存块和对象的内存块

    通过散列表管理引用计数的好处:

    • 对象的内存块不用再考虑内存块头部
    • 引用计数表记录着各内存块的地址,可以从各个记录追溯到各个对象的内存块

    autorelease

    顾名思义就是自动释放,相当于 C 语言的局部变量,当局部变量超出作用域时,自动变量就被废弃,不可再访问。调用该方法,将对象注册到 autorealeasePool 中,当 autorealeasePool 废弃,会自动调用 release 办法。

    autorelease 的具体使用办法:

    • 生成并持有 NSAutorealeasePool 对象
    • 调用已分配对象的 autorelease 方法
    • 废弃 NSAutorealeasePool 对象(已分配对象会自动调用 release 方法)
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
        
    id obj = [[NSObject alloc]init];
        
    [obj autorelease];
        
    [pool drain];
    

    另外,Cocoa 框架很多类方法用于返回 autorelease 的对象。比如 NSMutableArray 类的 arrayWithCapacity 类办法。

    id array = [NSMutableArray arrayWithCapacity:2];

    此源码相当于如下代码:

    id array = [ [NSMutableArray arrayWithCapacity:2] autorelease];

    autorelease 实现

    autorelease 的实现其实就是将对象加入 NSAutoreleasePool 中。

    -(id)autorelease
    {
        [NSAutoreleasePool addObject:self];
    }//该方法的实现,其实是使用一种特殊的方式来实现,即是使用了函数指针的调用方式,因为该办法的调用比较频繁。(具体可以学习 Runtime 方法之直接调用函数地址)
    
    -(void)addObject :(id)anObject
    {
        [array addObject:anObject];
    }
    
    -(void)drain
    {
        [self dealloc];
    }
    
    -(void)dealloc
    {
        [self emptyPool];
        [array release];
    }
    
    -(void)emptyPool
    {
        for (id obj in array) {
            [obj release];
        }
    }
    

    ARC 规则

    概要:实际上“引用计数式内存管理方式”的本质部分在 ARC 中并没有改变的。ARC 只是自动地帮我们管理内存了,其管理方式就是引用计数的方式。

    所有权修饰符

    OC 中的对象类型和 id 类型的变量,必须要附加所有权修饰符。类型有如下四中:

    • __strong
    • __weak
    • ______unsafe __unretained
    • __autoreleasing
    __strong 修饰符

    在 OC 中,对象类型和 id 类型的变量默认是附加上了 __strong 修饰符的。

    __strong 修饰符之"自己生成并持有的对象"

    //ARC 有效
    -(void)testARC
    {
        /**
         自己生成并持有对象 --> [[NSObject alloc]init]
         */
        id __strong obj = [[NSObject alloc]init];
        
        /**
         *  __strong 修饰符,为强引用,变量 obj 持有对象
         */
    }
    /**
     *  局部变量 obj 超出了作用域,所有强引用失效
     *  所以自动地释放自己持有的对象
     *  对象的所有者不存在,因此废弃该对象
     */
    

    __strong 修饰符之"非自己生成的对象,自己也能持有"

    -(void)testARC
    {
        id __strong obj0 = [[NSObject alloc]init];//对象A
        /**
            obj0 持有对象A的强引用
         */
        id __strong obj1 = [[NSObject alloc]init];//对象B
        /**
         *  obj1 持有对象B的强引用
         */
        id __strong obj2 = nil;
        /**
         *  obj2 不持有任何对象
         */
        
        obj0 = obj1;
        /**
         *  obj0 被持有对象B的 obj1 赋值,所以 obj0 持有对象B
            因为 obj0 被赋值,所以原来持有对象A的强引用失效
            对象A不再被任何对象持有,因此废弃对象A
         
            此时,持有对象的B的强引用变量为:obj1 obj0
         */
        
        obj2 = obj0;
        /**
         *  obj2 被持有对象B的 obj0 赋值,所以 obj2 持有对象B
         
            此时,持有对象的B的强引用变量为:obj1 obj0 obj2
         */
        
        obj1 = nil;
        /**
         *  因为 obj1 被赋值为 nil,所以持有对象B的强引用失效
         
            此时,持有对象的B的强引用变量为:obj2 obj0
         */
        
        obj0 = nil;
        /**
         *  因为 obj0 被赋值为 nil,所以持有对象B的强引用失效
         
            此时,持有对象的B的强引用变量为:obj2
         */
        
        obj2 = nil;
        /**
         *  因为 obj2 被赋值为 nil,所以持有对象B的强引用失效
         
            此时,没有变量持有对象的B的强引用,所以对象B被废弃
         */
    }
    

    __strong 修饰符之"成员变量"

    @interface YYTestObject : NSObject
    {
        id __strong _obj;
    }
    
    -(void)setObj:(id __strong)obj;
    
    @end
    
    -------------------------------------------------------------
    
    #import "YYTestObject.h"
    
    @implementation YYTestObject
    
    -(void)setObj:(id)obj
    {
        _obj = obj;
    }
    @end
    
    -------------------------------------------------------------
    
    -(void)testARC
    {
        id __strong test = [[YYTestObject alloc]init];
        
        [test setObj:[[NSObject alloc]init]];
    }
    
    /**
     *  self -(1)-> test -(2)-> _obj -(3)-> NSObject
     */
    //当 test 超出作用域后,强引用失效,之后的链接也跟着失效。
    
    __weak 修饰符

    通过上面对 __strong 修饰符的例子来看,貌似只要有__strong就可以很好地实现内存的管理,但是实际上不是这样的。__strong 修饰符 不很解决很严重的内存泄露问题 —循环引用。

    @interface YYTestObject : NSObject
    {
        id __strong _obj;
    }
    
    -(void)setObj:(id __strong)obj;
    
    @end
    
    -------------------------------------------------------------
    
    #import "YYTestObject.h"
    
    @implementation YYTestObject
    
    -(void)setObj:(id)obj
    {
        _obj = obj;
    }
    @end
    
    -------------------------------------------------------------
    
    -(void)testARC
    {
       id obj = [[YYTestObject alloc]init];//对象A
        [obj setObj:obj];
    }
    //obj 超出作用域,对对象A强引用失效,此时对象A的强引用变量有:obj的成员变量_obj
    

    通过Xcode自带的 instrument 检测循环引用如下:

    循环引用.png

    怎样才能消除循环引用呢?这时候应该引入__weak 修饰符了。

    __weak 修饰符不持有对象实例,提供弱引用。
    id __weak obj = [[YYTestObject alloc]init];//对象A
    
    //此源码编译器会发出警告。因为,为了不持有自己生成并持有的对象,生成的对象会立即被释放
    
    id obj = [[YYTestObject alloc]init];//对象A
    id __weak obj2 = obj;
    
    -----------------------------------------------------------
      
     -(void)testARC
    {
       id obj = [[YYTestObject alloc]init];//对象A
       id __weak obj2 = obj;
        [obj setObj:obj2];
    }
    //因为 obj 超出作用域,所以对对象A的强引用失效
    //对象A没有被持有,因此废弃对象A
    //obj 无持有者,因此也废弃对象 obj
    //此时,持有该对象 obj 弱引用的 obj2 变量的弱引用失效,nil 赋值给 obj2,使 obj2 没有成为悬挂指针
    
    ______unsafe __unretained

    ______weak 修饰符只是用于 iOS5 以上及 OS X Lion 以上的版本的程序。因此不在该范围的程序就使用 ____unsafe __unretained 来修饰。附有该修饰符的变量不属于编译器的内存管理对象,所以要注意赋值对象的所有者,否则会造成内存泄露或是程序崩溃。

    id __unsafe_unretained obj1 = nil;
        
        {
            id __strong obj0 = [[NSObject alloc]init];//对象A
            
            obj1 = obj0;
            
            NSLog(@"A: %@",obj1);
        }
        //因为 obj0 超出作用域,所以对对象A的强引用失效
        //对象A没有被持有,因此废弃对象A
        //obj0 无持有者,因此也废弃对象 obj
        //此时,持有该对象 obj 弱引用的 obj1 变量的弱引用失效
        NSLog(@"B: %@",obj1);
        //输出 obj1 变量表示的对象
        //obj1 变量表示的对象已经被废弃(悬挂指针),错误访问
    
    PS:如果 NSLog(@"B: %@",obj1); 访问成功的话,那只是碰巧而已,该区域的内存还没有被复用。这也说明了__unsafe __unretained 修饰的变量不会被赋值 nil ,以防止悬挂指针,所以访问者类型的变量,一定要确保对象是否存在,否则会导致程序崩溃。
    
    __autoreleasing

    在 ARC 有效的情况下,是不能使用 autorelease 方法,也不能使用 NSAutoreleasePool 类。这样一来,autorelease 无法直接调用,但实际上,ARC 有效的情况下,autorelease 方法是起到作用的。

    下面来看一下 __autoreleasing 显示使用情况:

    // 非ARC 
    {
      NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
        
        id __strong obj = [[NSObject alloc]init];
        
        [obj autorelease];
        
        [pool drain];
    }
    
    -----------------------------------------------------------------
    
    //ARC
    {
      @autoreleasepool {
            id __autoreleasing obj = [[NSObject alloc]init];
            //__autoreleasing --> 调用 autorelease 方法 --> 意味着 obj 已经注册到 autoreleasepool 中
        }
    }
    

    值得注意的是,显示地添加 ______autoreleasing 修饰符同显示添加____strong 修饰符一样少见。所以下面简述一下其隐式的使用情况:

    • 取得非自己生成并持有的对象,即是除了 alloc / new / copy / mutableCopy 等方法取得对象,一般是类办法。
    • 对象作为返回值
    • __weak 修饰的对象
    • id 的指针或是对象的指针

    (1) 取得非自己生成并持有的对象

    编译器会检查方法名是否以 alloc / new / copy / mutableCopy 开始,如果不是则自动将返回值的对象注册到 autoreleasepool 中。

    @autoreleasepool {
         //取得非自己生成并持有的对象 --> [NSArray array]
            id __strong *array = [NSArray array];
            //因为 array 为强引用,所以持有对象,
            //并且该对象有编译器判断其方法名后,自动注册到 autoreleasepool 中
    }
    //对象 array 超出作用域,所以强引用失效,同时自动释放自己所持有的对象
    //同时,随着 @autoreleasepool 快的结束,注册到 autoreleasepool 中的所有对象被自动释放。
    //因为对象的不存在,所以废弃对象
    
    //持有关系:autoreleasepool --> array --> [NSArray array]
    

    (2) 对象作为返回值

    第(1)条中,不显式地使用 __autoreleasing 也能使对象注册到 autoreleasepool 中,下面我们来看下取得非自己生成并持有的对象时被调用方法的源码代码示例:

    +(id) array
    {
        //方案一
        return [[NSArray alloc]init];
        
        //方案二
        id obj = [[NSArray alloc]init];
        return obj;
    }
    
    //由于 return 超出作用域,所以该强引用对应的自己持有的对象会被自动释放,但该对象作为返回值,所以编译器会自动将该对象注册到 autoreleasepool 中。
    

    (3) __weak 修饰的对象

    ______weak 修饰的对象是对某对象持有弱引用,如果要访问某对象的时候,该对象有可能已经被废弃了,这样会导致程序崩溃。因此,______weak 修饰的变量必定要注册到 autoreleasepool 中,在 @autoreleasepool 快结束之前,都能确保该对象的存在。

    id __weak obj1 = obj0;
    NSLog(@"obj: %@",obj1);
    
          相等于
           ||
           ||
            
    id __weak obj1 = obj0;
    id __autorealeasing tmp = obj1;
    NSLog(@"obj: %@",tmp);
    

    (4) id 的指针或是对象的指针

    对象的指针,即是获得对象的地址。形式如:NSObject **obj 。

    同前面讲述的 id obj 和 id ______strong obj 完全一样。那么 id 的指针 id *obj 又如何?可以由 id ______strong obj 的例子类推出 id ______strong *obj 吗?其实推出来的是 id __autorealeasing *obj。

    同样的 NSObject **obj 便成为了 ,NSObject * __autorealeasing *obj。

    在开发中,我们常常需要得到详细的错误信息,经常会在方法的参数中传递 NSError 对象的指针,而不是函数返回值。

    NSError *error = nil;
    BOOL result = [myObject performOperationWithError:&error];
    
    //方法的声明
    -(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
    
    //根据前面所讲述的,除了 alloc / new / copy / mutableCopy 办法返回值取得的是对象是自己生成并持有的,其他的方法都是非自己生成并持有的。所以 performOperationWithError 办法需要获取的参数 error 对象,都会注册到 autoreleasepool,并取得非自己生成并持有的对象。
    
    值得一提的是:赋值给对象指针的所有权修饰符必须一致。
    
    (❌)NSError *error = nil;
        NSError **pError = &error;
        
    (✅)NSError *error = nil;
        NSError * __strong *pError = &error;
        
        那么问题来了,上面个的例子中:源码如下
        NSError *error = nil;
        BOOL result = [myObject performOperationWithError:&error];
    
        //方法的声明
        -(BOOL)performOperationWithError:(NSError * __autoreleasing *)error;
        
        传递给 performOperationWithError 办法的参数 error 的修饰符是 __strong,而办法接受值是由 __autoreleasing修饰的,为什么编译没有报错呢?
        其实实际上编译器已经将传递参数的那部分源码转化成如下的形式了:
        NSError * __strong error;
        NSError * __autoreleasing tmp = error;
        BOOL result = [myObject performOperationWithError:&tmp];
        error = tmp;
    

    具体也可以参考苹果的官方文档:苹果文档

    最后,如果想打印 autoreleasePool 中有哪些对象,不管是在 ARC 或是非 ARC 的情况下,都可以调用非公开的 _objc_autoreleasePoolPrint();

    既然简单地介绍了 ARC 一些简单用法,下面就看看在 ARC 下,有哪些规则吧。

    • 不能使用 retain / release / retainCount / autorelease
    • 不能使用NSAllocateObject / NSDeallocateObject
    • 必须遵守内存管理的方法命名
    • 不要显示调用 dealloc
    • 使用 @autoreleasepool 块代替 NSAutoreleasepool
    • 不能使用区域(NSZone)
    • 对象型变量不能作为C语言的结构体(struct / union)的成员
    • 显示转换 “id” 和 “void *”

    上面的一些规则中,挑几条值得注意的来讲述一下:

    (1)必须遵守内存管理的方法命名

    init 开头的方法,必定是要返回对象,对象应该是 id 类型或是该方法声明类的对象类型,或者是该类的超类或子类型。该返回的对象是不会注册到 autoreleasepool 中的。基本只是对 alloca 方法返回值的对象进行初始化并返回该对象的。

    -(id)initWithObject;    (✅)
    
    -(void)initWithObject;  (❌) --->返回值
    
    -(id)initialize;        (✅) --->应是驼峰式命名
    

    为了使能够让手动管理内存和 ARC 管理内存两者之间互相操作,属性名不能以 new 开头命名,除非你重新命名该属性的 getter 办法。

    // Won't work:
    @property NSString *newTitle;
     
    // Works:
    @property (getter=theNewTitle) NSString *newTitle;
    

    (2) 不要显示调用 dealloc

    当对象废弃的时候,都会调用该办法的。在该办法中大多数只使用删除已注册的代理和观察者对象即可。

    // 非 ARC
    -(void)dealloc
    {
        //do something
        
        [super dealloc];//一定要注意调用的顺序
    }
    
    // ARC
    -(void)dealloc
    {
        //do something
       
       // [super dealloc];在 ARC 下,无需显示调用该方法, ARC 会自动处理的。
    }
    

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

    struct Data
    {
        NSMutableArray *array;
    };
    

    如果结构体中的成员变量出现 Objective - C 对象,便会引起编译错误。这是因为C语言的规约上没有方法来管理结构体成员的生存周期。C语言的局部变量可使用该变量的作用域来管理对象,而 ARC 吧内存管理的工作分配给编译器的。不过实在需要,也可以改成如下:

    struct Data{

    NSMutableArray ______unsafe____unretained *array;

    };

    (4) 显示转换 “id” 和 “void *”

    在 ARC 下,我们有时候需要将 Core Foundation 对象转换成一个 Objective-C 对象,这时候我们就应该告诉编译器,在转换的过程中引用计数需要如何调整,这时候我们就应该使用到与 bridge 有关的关键字了。一下是这些关键字的说明;

    • __bridge :只做类型转换,不修改相关对象的引用计数。
    • ______bridge____retained:类型转换后,相关对象的引用计数+1
    • ______bridge____transfer:类型转换后,该对象的引用计数由 ARC 管理,被转换的对象随后释放。
    CFMutableArrayRef cfArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    id __strong obj = (__bridge id)cfArray;
    //CFGetRetainCount(cfArray); --->2
    
    ---------------------------------------------------------------
      
    id obj = [[NSObject alloc]init];
    CFMutableArrayRef cfArray = (__bridge__retained CFMutableArrayRef)obj;
    //CFGetRetainCount(cfArray); --->2
    
    ---------------------------------------------------------------
    
    CFMutableArrayRef cfArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    id __strong obj = (__bridge__transfer id)cfArray;
    //CFGetRetainCount(cfArray); --->1
    转换成非 ARC
    CFMutableArrayRef cfArray = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
    id obj = (id)cfArray;
    [obj retain];  
    [(id)cfArray release];
    注意:非 ARC id 变量强转换成 void* 变量是并不会出问题。
    

    ARC 的实现

    苹果官方说明中称,ARC 是“由编译器进行内存管理”的,但是实际上只有编译器是无法完胜的,需要借助运行库的协助。

    __strong
    {
        id __strong obj = [[NSObject alloc]init];
    }
    以上的代码用编译器的模拟代码如下:
      id obj = objc_msgSend(NSObject,@selector(alloc));
      objc_msgSend(obj,@selector(init));
      objc_release(obj);
    
    {
      id __strong obj = [NSArray array];
    }
    以上的代码用编译器的模拟代码如下:
    id obj = objc_msgSend(NSArray,@selector(array));
    objc_retainAutoreleasedReturnValue(obj);
    objc_release(obj);
    
    +(id)array
    {
        return [[NSMutableArray alloc]init];
    }
    以上的代码用编译器的模拟代码如下:
    +(id)array
    {
        id obj = objc_msgSend(NSMutableArray,@selector(alloc));
        objc_msgSend(obj,@selector(init));
        return objc_autoreleasedReturnValue(obj);
    }
    

    objc_autoreleasedReturnValue 函数将对象注册到 autoreleasePool 中,与 autorelease 函数是有着不同之处的。objc_autoreleasedReturnValue 会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调用方在调用了方法或函数后紧接着调用 objc_retainAutoreleasedReturnValue 函数,那么就不会将对象注册到 autoreleasePool 中,而是直接传递到方法或是函数调用方。objc_retainAutoreleasedReturnValue 与 objc_retain 函数不同,它即便不注册到 autoreleasePool 中,也是能够正确获取对象的。这样一来,就少了对象注册到 autoreleasePool 中而直接传递了。

    __weak
    {
       id __weak obj1 = obj;
       NSLog(@"obj = %@",obj);
    }
    
    //以上的代码用编译器的模拟代码如下: 
    id obj1;
    objc_initWeak(&obj1,obj);// --->转化成:将有 __weak 修饰的变量初始化为0后,即是 id obj1 = 0,objc_storeWeak(&objc1, obj);
    
    id tmp = objc_loadWeakRetained(&obj1);
    objc_autorelease(tmp);
    NSLog(@"obj = %@",tmp);
    
    objc_destroyWeak(&obj1); // --->转化成:objc_storeWeak(&objc1, 0);
        
    //objc_storeWeak 函数把第二参数的赋值对象的地址为键值,将第一参数的附有 __weak 修饰符的变量的地址注册到 weak 表中。如果第二个参数为0,则把变量从 weak 表中删除。
    

    问题1:weak 表的作用?

    • 当对象被废弃了,在把 __weak 的变量从 weak 表中删除时,将变量的地址,赋值为 nil;

    问题2:使用 __weak 修饰的变量,是从 weak 表中还是从 autoreleasePool 中获取对象?

    • 在使用 __weak 的变量时,一方我们可以通过 weak 表获取变量的地址而找到对应的对象,一方也可以从注册在 autoreleasePool 中去获取,但从模拟编译器的代码 NSLog(@"obj = %@",tmp); 可以看出,应该是从后者去获取到对象。

    注意: ______weak 修饰的变量被每一使用一次,所赋值的对象都会被注册到 ______autoreleasePool 中一次,所以为了减少对内存的使用,将附有 ______weak 修饰的变量赋值给附有 ______strong 修饰的变量后在使用可以避免此类问题。在 block 中使用也是该这样使用的。

    __autoreleasing
    @autoreleasepool {
            id __autoreleasing obj = [[NSObject alloc]init];
     }
     //以上的代码用编译器的模拟代码如下:
    
     id pool = objc_autoreleasePoolPush();
     id obj = objc_msgSend(NSObject,@selector(alloc));
     objc_msgSend(obj,@selector(init));
     objc_autorelease(obj);
     objc_autoreleasePoolPop(pool);
    
    @autoreleasepool {
            id __autoreleasing obj = [NSArray array];
        }
     //以上的代码用编译器的模拟代码如下:   
     id pool = objc_autoreleasePoolPush();
     id obj = objc_msgSend(NSArray,@selector(array));
     objc_retainAutoreleasedReturnValue(obj);
     objc_autorelease(obj);
     objc_autoreleasePoolPop(pool);
    

    尾巴:通过介绍引用计数式的内存管理方式如何使用,到它们是如何实现,再到如何利用引用计数式的内存管理方式去实现 ARC。

    参考资料:

    Objective-C 高级编程

    苹果文档

    相关文章

      网友评论

        本文标题:引用计数内存管理和 ARC 那点事

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