内存管理(四) - 源码解读所有权修饰符(一)

CSDN

有道云

__strong 修饰符

1. alloc/new/copy/mutableCopy

赋值给附有__strong 修饰符的变量在实际的程序中到底是这么样运行的呢?

{
    id __strong obj = [[NSObject alloc] init];
}

该源码实际上可以转换为调用以下函数

id obj = objc_msgsend(NSObject, @selector(alloc));
objc_msgSend(obj, @selectro(init));
objc_release(obj);

2次调用了objc_msgSend方法, 变量作用域结束时通过 objc_release 释放对象。虽然 ARC 时不能使用 release 方法,但由此可知编译器自动插入了 release

2. alloc/new/copy/mutableCopy 之外

{
     id __strong obj = [NSMutableArray array];
}

虽然调用了我们熟知的 NSMutableArray 类的 array 类方法,但得到的结果却与之前稍有不同。

id obj = objc_msgsend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
objc_retainAutoreleasedReturnValue 函数

objc_retainAutoreleasedReturnValue 函数主要用于最优化程序运行。

顾名思义,它是用于自己持有对象的函数,但它持有的对象应该返回注册在 autoreleasePool 中对象的方法,或者是函数的返回值。像这种,非alloc/new/copy/mutableCopy 的方法,比如 NSMutableArray 类的 array 类方法等调用之后,由编译器插入该函数。

这种 objc_retainAutoreleasedReturnValue 函数是成对的,与之相对的函数是 objc_autoreleaseReturnValue

objc_autoreleaseReturnValue 函数

objc_autoreleaseReturnValue 用于 非alloc/new/copy/mutableCopy 返回对象的实现上。

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

源码如下, 调用看 objc_autoreleaseReturnValue 函数

+ (id) array {
   id obj = objc_msgsend(NSMutableArray, @selector(alloc));
   objc_msgSend(obj, @selectro(init));
   retrun objc_autoreleaseReturnValue(obj);
}

像该源代码这样,通过使用 objc_autoreleaseReturnValue 函数,返回注册到 autoreleasePool 中的对象。

但是 objc_autoreleaseReturnValue 函数 与 objc_autorelease 函数不同,一般不仅限于注册到 autoreleasePool 中。

objc_autoreleaseReturnValue 函数会检查使用该函数的方法或者函数调用方的执行命令列表,如果!!! 在方法或者函数的调用方在调用了方法或者函数后紧接着调用了 objc_retainAutoreleasedReturnValue函数,那么就不将返回的对象注册到 autoreleasePool 中,而是直接传递到方法或者函数的调用方。

小结

objc_retainAutoreleasedReturnValue 函数与 objc_retain 函数不同,它即便不注册到 autoreleasePool 中而返回对象,也能够正确的获取对象。

通过 objc_retainAutoreleasedReturnValueobjc_autoreleaseReturnValue 函数的协作,可以将对象不注册到 autoreleasePool 中而直接传递, 这一过程到达了最优化。

__weak 修饰符

就像前面我们看到的一样, __wea 修饰符提供的功能如同魔法一般。

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

这些功能就像魔法一样,到底发生了什么,我们一无所知。

{
    id __weak obj1 = obj;
}

假设变量 obj 附有 __strong 修饰符且对象被赋值.


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

通过 objc_initWeak 函数初始化附有 __weak 修饰符的变量。

在变量作用域结束时通过 objc_destroyWeak 函数释放该变量。

如下面代码所示:

objc_initWeak 函数将附有 __weak 修饰符的 变量obj1 初始化为 0 后,会将赋值的对象obj作为参数调用 objc_storeWeak 函数。

obj1 = 0;

objc_storeWeak(&obj1, obj);

objc_destroyWeak 函数将0做参数调用 objc_storeWeak 函数

objc_storeWeak((&obj1, obj)

即使前面的源码和下面源代码相同

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

objc_storeWeak 函数

objc_storeWeak 函数把第二参数的赋值对象的地址作为键值,将第一个参数附有__weak 修饰符的变量的地址注册到 weak 表中。如果第二参数为0,则把变量的地址从 weak 表中删除。(验证我前面的理解)

weak 表与引用计数表相同,作为散列表被实现。如果使用 weak表,将废弃对象的地址作为键值进行检索,就能高效地获取对应的附有 __weak 修饰符的变量的地址。另外,由于一个对象可同时赋值给多个附有 __weak 修饰符的变量中,所以对于一个键值,可注册多个变量的地址。

释放对象

释放对象时,废弃谁都不持有的对象的同时,程序的动作是怎样的呢?下面我们来跟踪观察。对象将通过 objc_release 函数释放。

(1) objc_release

(2) 因为引用计数为0所以执行 dealloc

(3) _objc_rootDealloc

(4) objc_dispose

(5) objc_destructInstance   //销毁实例

(6) objc_clear_deallocating //清除回收

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

(1) 从 weak 表中获取废弃对象的地址为键值的纪录。

(2) 将包含在记录中的所有附有 __weak 修饰符变量的地址,赋值为nil。

(3) 从 weak 表中删除该纪录。

(4) 从引用计数表中删除废弃对象的地址为键值的纪录。

根据以上步骤,前面说的如果附有 __weak 修饰符的变量所引用的对象被废弃,则将 nil 值赋给该变量这一功能即被实现。由此可知如果有大量使用附有 __weak 修饰符的变量,则会消耗相应的 CPU 资源。良策是只在需要避免循环引用时使用 __weak 修饰符。

使用 __weak 修饰符的修饰自己生成并持有的对象

{
  id __weak obj = [[NSObject alloc] init];
}

将自己生成并持有的对象赋值给附有 __weak 修饰符的变量,所以自己并不能持有该对象,这时会被释放并被废弃,因此会引起编译器警告。

id obj ;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_initWeak(&obj, tmp);
obj_release(tmp);
objc_destroyWeak(&obj);

虽然自己生成并持有的对象通过 objc_initWeak 函数被赋值给附有 __weak 修饰符的变量中,但编译器判断其并没有持有者,故该对象立即通过 objc_release 函数被释放和废弃。

你可能感兴趣的:(书籍,iOS)