Object-C在MRC文件中使用弱指针
背景:
-
在ARC文件中,我们通常会在
block
中使用__weak
生成对象的弱引用,解决block
中对象循环引用的问题。 -
新项目建议完全使用ARC,不过有一些老项目和老文件还在使用MRC,并且改造难度比较大。
-
那么问题来了,MRC文件中
__weak
关键字不会有任何作用(特别注意用了也不会有编译错误),MRC文件中如何实现ARC下weak
的能力?
结论
这里先给出结论,一些细节的讨论放在后面
这里给出两种方法,有更好方案欢迎讨论
- 创建一个ARC文件,在里面实现一个WeakPtr的包装类
- 使用
obj_storeWeak
、obj_loadWeak
在MRC文件直接使用弱指针或用于实现上述包装类
个人推荐使用方法1,简单直接,不容易出错,这里给出一个实现的示例
// ArkWeakPtr.m
// 这个文件使用ARC编译
@interface ArkWeakPtr : NSObject
- (id)init:(id)ptr;
- (id)lock;
@end
//----- ArkWeakPtr -----
@implementation ArkWeakPtr
{
__weak id _weakPtr;
}
-(id) init:(id)ptr
{
self = [super init];
if (self) {
_weakPtr = ptr;
}
return self;
}
// 返回对象的强引用,如果对象已释放,返回nil
-(id) lock
{
__strong id strong = _weakPtr;
return strong;
}
@end
使用示例:
// MyClass.m
// 这个文件使用MRC编译
@interface MyClass : NSObject
@end
@implementation MyClass
{
ArkWeakPtr* _weakSelf; //
}
-(id) init {
self = [super init];
if (self) {
_weakSelf = [[ArkWeakPtr alloc] init:self];
}
return self;
}
-(void) dealloc {
// 异步block会强引用_weakSelf,这里不会真正释放_weakSelf
[_weakSelf release];
[super dealloc];
}
-(void) doSomething {
ArkWeakPtr* weakSelf = _weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 1. block会强引用weakSelf,在block中访问weakSelf是安全的
// 2. block通过weakSelf弱引用self,self可以正常释放
// 3. 如果self已释放lock后_self等于nil
// 4. block运行完成后weakSelf也会正常释放
typeof(self) _self = [weakSelf lock];
if (_self) {
NSLog(@"doSomething");
}
});
}
问题
-
为什么可以使用ARC来实现MRC的弱引用?
从对象的能力角度来讲ARC/MRC没有区别,ARC只是编译器在编译时自动帮忙生成了retain/release等调用,两者最终生成的汇编代码并没有什么不同。关键时候通过查看汇编代码可以明确很多实现原理。
-
对于上述方法1,能否在MRC文件中实现?
不使用
obj_storeWeak
/obj_loadWeak
时,无法做到线程安全。仅当判断对象是否已经释放以及给对象增加引用计数两个操作是原子操作时,才能保证多线程访问时,返回的对象不是一个野对象。否则可能出现判断当前对象还没有释放,然后对象被释放,最后对象增加引用。 -
不使用弱指针机制是否可以解决问题?
当然可以,通过暴露一个cancel接口给外部对象用于取消异步操作,或在特定时机通过主动release解引用都可以,不过代码维护成本更高,开发者需要精确了解对象释放的时机。如果存在多次回调,或者不回调的场景,开发难度会更高。
题外话
-
当使用
ARC
与c++
混编时,把一个obj放到c++ STL模板容器中(比如std::vector),能否保证引用记数是正确的?可以保证。与NSArray相似,当对std::vector调用push_back时,会自动增加对象的引用计数。
-
block中引用的obj,生命期是怎么管理的?
不管是ARC还是MRC,block中使用到的obj都会被block强引用,block释放时会自动减引用。因此obj在传给block前,不需要再自己retain,但注意可能出现的循环引用问题。因此有一种解决方法是把block引用的obj设置为
__block
类型,通过在block中主动释放的方式解循环引用