iOS 开发每天分享优质文章

[iOS]关于ARC实现的一些总结

2018-08-23  本文已影响201人  未来行者

最近在重温《Objective-C 高级编程》这本书,深深为这本薄书里蕴含的"惊人"能量所倾倒.本篇文章将总结一下ARC实现的一些细节.

ARC的概念

ARC翻译过来就是自动引用计数,相信大家都知道.苹果官方文档中说明:ARC是由由编译器进行内存管理的;本书指出了一个观点:实际上只有编译器是无法完全胜任的,在此基础上还需要Objective-C运行时库的协助.ARC的核心就是在对象需要释放的地方自动插入release.
说起ARC,就逃不过讨论几个修饰符:__strong,__weak,__autoreleasing和引用计数,下面一一来进行细节说明.

__strong

先看如下一段代码:

{
// ARC中默认会在对象前添加一个修饰符__strong
id obj = [[NSObject alloc] init];
//<==>等价于
id __strong obj = [[NSObject alloc] init];
}

根据runtime特性,它的实际调用如下:

{
// 消息转发
id obj = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
// 编译器在obj作用域结束时自动插入release
objc_release(obj);
}

当然这里是以alloc/new/copy/mutableCopy生成的对象,这种对象会被当前的变量所持有,引用计数会加1.那如果不是用被持有的方式生成对象呢?
看下面这段代码:

{
id obj = [NSMutableArray array];
}

这种方式生成的对象不会被obj持有,通常情况下会被注册到autoreleasepool中.但也有特殊情况,上面的代码可以转换成如下代码:

{
// 消息转发
id obj = objc_msgSend(NSMutableArray,@selector(array));
// 调用objc_retainAutoreleasedReturnValue函数
objc_retainAutoreleasedReturnValue(obj);
// 编译器在obj作用域结束时自动插入release
objc_release(obj);
}

这里介绍两个相关函数:

__weak

weak修饰符想必大家都非常熟悉,它有一个众所周知的特性:用weak修饰的对象在销毁后会被自动置为nil.另外还补充一点:凡是用weak修饰过的对象,必定是注册到autoreleasepool中的对象.
看下面的代码:

{
// obj默认有__strong修饰
id obj = [[NSObject alloc] init];
id __weak obj1 = obj;
}

实际过程如下:

{
// 省略obj的实现
id obj1;
// 通过objc_initWeak初始化变量
objc_initWeak(&obj1,obj);
// 通过objc_destroyWeak释放变量
objc_destroyWeak(&obj1);
}

那么weak表中的对象是如何被释放的呢?

这就是__weak修饰的变量会在释放后自动置为nil的原因.同时,因为weak修饰之后涉及到注册到weak表等相关操作,如果大量使用weak可能会造成不必要的CPU资源浪费,所以书里指出尽量在循环引用中使用weak.
这里不得不提到另外一个和__weak相近的属性:__unsafe_unretained,它与weak的区别在于,释放对象后不会对其置为nil,在某些特定的场合下,需要延迟释放的时候,可以考虑用这个属性修饰.

好了,下一个问题,看如下代码:

{
id __weak obj1 = obj;
// 这里使用了obj1这个用weak修饰的变量
NSLog(@"%@",obj1);
}

在weak变量被使用的情况下,实际过程如下:

{
id obj1;
objc_initWeak(&obj1,obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@",tmp);
objc_destroyWeak(&obj1);
}

从这段实现代码中我们可以看出如下几点:

{
id __weak obj1 = obj;
id tmp = obj1;
// 后面使用tmp即可
}

延伸一下:为什么有循环引用block内用weakObject的时候最好能在block内套一层strongObject?

__autoreleasing

它的主要作用就是将对象注册到autoreleasepool中.没啥好说的.

最后补充几种在ARC环境下获取引用计数的方法,但并不一定准确:ARC的一些引用计数优化,以及多线程的中的竞态条件问题,有兴趣的可以自己去了解一下.

(1) 使用_objc_rootRetainCount()私有函数
OBJC_EXTERN int _objc_rootRetainCount(id);
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id obj = [[NSObject alloc] init];
    NSLog(@"%d",_objc_rootRetainCount(obj));
}
@end

(2) 使用KVC
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id obj = [[NSObject alloc] init];
    NSLog(@"%d",[[obj valueForKey:@"retainCount"] integerValue]);
}
@end

(3) 使用CFGetRetainCount()
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    id obj = [[NSObject alloc] init];
    NSLog(@"%d",CFGetRetainCount((__bridge CFTypeRef)(obj)));
}
@end
上一篇 下一篇

猜你喜欢

热点阅读