为什么使用weak修饰的变量会自动设置为nil

Posted by 三十一 on June 26, 2017
--- layout:     post title:      为什么使用weak修饰的变量会自动设置为nil subtitle:   在不断填坑中前进。。 date:       2017-06-26 author:     三十一 header-img: img/post-bg-nextgen-web-pwa.jpg header-mask: 0.3 catalog:    true tags:    - 内功    - iOS ---

为什么使用weak修饰的变量会自动设置为nil

Objective-C高级编程 iOS与OS X多线程和内存管理 读书笔记

为什么使用weak修饰的变量会自动设置为nil

我们知道:

  • 当使用__weak修饰符的变量的 引用对象 被废弃时,则将nil赋值给该变量。
  • 使用附有 __weak 修饰符的变量,即是使用注册到 autoreleasepool 中的对象。

系统是如何实现的?

先看下面的代码:

    {
        id __weak obj1 = obj;
    }

经过编译器会装换为下面的代码;

    id obj1;
    objc_initWeak(&obj1,obj);
    objc_destroyWeak(&obj1);

其中 objc_initWeak 函数初始化附有 __weak 修饰符的变量,在变量作用域结束后通过 objc_destroyWeak 函数释放该变量。

objc_initWeak 实现:

    obj1 = 0;
    objc_storeWeak(&obj1,obj);

objc_destroyWeak 实现:

    objc_storeWeak(&obj1,0);

所以以上的代码与下面的源代码相同:

    id obj1;
    obj1 = 0;
    objc_storeWeak(&obj1,obj);
    objc_storeWeak(&obj1,0);

objc_storeWeak 函数把第二个参数的赋值对象的地址作为键值,将第一个参数的附有 __weak 修饰符的变量的地址注册到 weak 表中。

*{obj:&obj1} 等同 {“key”:”Value”}*

weak 表 是作为一个散列表实现的。 当 变量 废弃时,使用该对象的地址作为键进行查找,就能快速的获取对应的 __weak 修饰符的变量的地址。另外,由于一个对象可以同时赋值给多个 附有 __weak 修饰符的变量中,所以对于一个键,可注册多个变量的地址。

释放对象时,废弃谁都不持有的对象的同时,程序的动作是怎么样的呢?

  1. objc_release
  2. 因为引用计数为0所以执行 dealloc
  3. _onjc_rootDealloc
  4. objc_dispose
  5. objc_destrucInstance
  6. objc_clear_deallocating

对象废弃时 最后调用的 objc_clear_deallocating 函数动作如下:

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

所以如果附有 __weak 修饰符的变量 所引用的对象废弃后,将 nil 赋值给改变量的步骤是在这里实现的。

由此可知,如果大量的使用附有 __weak 修饰符的变量,会消耗对应的 CPU 资源。良策是只在需要避免循环引用时使用 __weak 修饰符。

最后总结

简单来说就是:

  1. 使用 __weak 修饰符的变量 A ,A 引用变量 B ,这时系统会把 B 的地址作为Key ,A的地址作为 Value ,存储在一个系统的散列表中;
  2. 当对象 B 被释放时,会先调用 release 然后引用计数为0;
  3. 然后调用 dealloc ;
  4. 接着会调用一个名为objc_clear_deallocating的函数,这个函数的作用就是查找以 B 的地址为 key 的 Value,然后将改这些 Value 赋值为 nil,在把这些 Key 、Value 从散列表中删除;
  5. 最后在引用计数表中将以 B 的地址为键值的记录删除。