Fix unretain-unsafe Pointer


最近被迫在大量的使用__unsafe_unretained pointer,用起来坑太多了 先看一段必然会crash的代码:

// Do any additional setup after loading the view.
 _welcomeView = [[ATCSearchLivingWelcomeView alloc]initWithFrame:CGRectMake(0, 0, 100,100)];
 [self.view addSubview:_welcomeView];
 
 __unsafe_unretained MXSecondViewController* unsafeSelf = self;
 [_welcomeView startAnimatingWithCompletionBlock:^{
     [unsafeSelf internalMethod];
 }];

假设welcomeView的animation执行10秒,那么animation结束前,释放controller,必然会crash。原因也很简单,由于unsafeSelf的类型是__unsafe_unretained的,那么它所指向的对象在dealloc之后,自己也不会nil,因此,unsafeSelf变成了野指针。

通常这种问题解决的办法是当controller dealloc的时候将回调的block手动释放:

- (void)dealloc{
    _welcomeView.block = nil;
}

但是这种方法治标不治本而已,总不能将block全部暴露出来手动释放一遍,因此,我们需要一种通用的解决方法。

我们先来分析下,问题产生的原因:

多数情况下使用__unsafe_unretained指针是由于在iOS5.0下无法使用__weak,但又要解决使用block产生的retain-cycle。就上面那个例子来说,对象间的引用关系如左图,当Controller释放后,对象间关系变成了右图:

解决这个问题,一种通用的解决方案来是mike ash的这边文章MAZeroingWeakRef

使用方法如下:

MXT_ZeroingWeakRef* ref = [[MXT_ZeroingWeakRef alloc]initWithTarget:self];
[_welcomeView startAnimatingWithCompletionBlock:^{

    [ref.target internalMethod];
}];

MXT_ZeroingWeakRef是我对MAZeroingWeakRef的精简和改写,去掉了CoreFoundation对象的兼容。但是思路基本上是一致的。这种用法和C++0x或BOOST库中的智能指针类似:

std::weak_ptr<DataBase> DBObserver = self;

里面实现的思路有些绕,确实要花一点时间才能把它完全理清楚。最关键的一点是”isa-swizzling”,也就是apple实现KVO的办法,理解了这个,剩下的就迎刃而解了。

Further Reading