专栏名称: 唤之
目录
相关文章推荐
OSC开源社区  ·  宇树王兴兴早年创业分享引围观 ·  昨天  
OSC开源社区  ·  升级到Svelte ... ·  2 天前  
程序猿  ·  “未来 3 年内,Python 在 AI ... ·  2 天前  
待字闺中  ·  DeepSeek 爆火带来的大变化 ·  1 周前  
51好读  ›  专栏  ›  唤之

Objective-C 之ARC实现

唤之  · 掘金  · 程序员  · 2017-12-08 08:25

正文

苹果的官方说明中称,ARC是“由编译器进行内存管理”的,但是实际上只有编译其是无法完全胜任的,再次基础上还需要Objective-C运行时库的协助。 也就是说,ARC由以下工具、库来实现。

  • clang(LLVM编辑器)
  • objc4 Objective-C 运行时库

__strong 修饰符

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

~~本人是c/cpp小白,没有编译成功,没看到汇编输出TAT~~

以上代码编译器的模拟代码:

/** 编译器的模拟代码 */
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);

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

使用alloc/new/copy/mutableCopy以外的方法时:

{
	id __strong obj = [NSMutableArray array];
}

编译器的模拟代码如下:

/** 编译器的模拟代码 */
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);

其中的 objc_retainAutoreleasedReturnValue 函数主要用于优化程序运行,它用于自己持有(retain)对象的函数,但它持有的对象应为返回注册在autoreleasepool中对象的方法,或是函数的返回值。在调用alloc/new/copy/mutableCopy以外的方法,由编译器插入该函数。

objc_retainAutoreleaseReturnValue 函数相对的函数为 objc_autoreleaseReturnValue 函数。它用于alloc/new/copy/mutableCopy方法以外的NSMutableArray类的array类方法等返回对象的实现上。

+ (id) array
{
	return [[NSArray alloc] init];
}

转换后

+ (id) array
{
	id obj = objc_msgSend(NSArray,@selector(alloc));
	objc_msgSend(obj,@selector(init));
	return objc_autoreleaseReturnValue(obj);
}

返回注册到autoreleasepool中对象的方法使用了 objc_autoreleaseReturValue 函数返回注册到autoreleasepool中的对象。但是 objc_autoreleaseReturValue 函数同 objc_autorelease 函数不同,一般不仅限于注册对象到autoreleasepool中。

objc_autoreleaseReturValue 函数会检查使用该函数的方法或函数调用方的执行命令列表。如果方法或函数的调用方在调用了方法或函数后紧接着调用 objc_retainAutoreleasedReturnValue() 函数,那么就不将返回的对象注册到autoreleasepool中,而直接传递到方法或函数的调用方。 objc_retainAutoreleasedReturnValue 函数与 objc_retain 函数不同,它即便不注册到autoreleasepool中而返回对象,也能够正确地获取对象。

通过 objc_autoreleaseReturnValue 函数和 objc_retainAutoreleasedReturnValue 函数的协作,可以不讲对象注册到autoreleasepool中而直接传递,这一过程达到了最优化。


__weak 修饰符

  • 若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量。
  • 使用附有__weak修饰符的变量,即是使用注册到了autoreleasepool中的对象。
{
	id __weak obj1 = obj;
}
/** 编译器的模拟代码 */
id obj1;
objc_initWeak(&obj1,obj);
objc_destroyWeak(&obj1);

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

如以下源码所示, objc_initWeak 函数将附有 __weak 修饰符的变量初始化为0后,会将赋值的对象作为参数调用objc_storeWeak函数。

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

objc_destroyWeak 函数将0作为参数调用objc_storeWeak函数。

objc_storeWeak(&obj1,0);

即前面的源代码和以下代码相同:

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

objc_weakStore函数把第二参数的复制对象的地址作为键值,将第一参数的附有__weak修饰符的变量的地址注册到weak表中,如果第二参数为0,则把变量的地址从weak表中删除。

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

释放对象时,废弃谁都不持有的对象,通过objc_release函数释放。

  1. objc_release
  2. 因为计数为0,所以执行dealloc
  3. _objc_rootDealloc
  4. object_dispose
  5. objc_destructInstanse
  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];
    NSLog(@"obj = %@",obj);
}

编译结果如下:

Assigning retained object to weak variable; object will be released after assignment

编译器模拟代码如下:

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

运行结果如下:

2017-12-07 19:37:24.075939+0800 ImageOrientation[10963:3581164] obj = (null)

使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象。

{
	id __weak obj1 = obj;
	NSLog(@"%@",obj1);
}

该代码可以转换为如下形式:

/** 编译器模拟代码*/
id obj1;
objc_initWeak(&obj1,obj);
id temp = objc_loadWeakRetained(&obj1);
objc_autorelease(temp);
NSLog(@"%@",obj1);
objc_destroyWeak(&obj1);

与赋值时相比,在使用附有 __weak 修饰符变量的情形下,增加了对 objc_loadWeakRetained 函数和 objc_autorelease 函数的调用。这些函数的动作如下:







请到「今天看啥」查看全文