我遇到一种情况,需要用访问该对象的完成块初始化对象。为了使这种访问成为可能,对象被定义为__block。问题是这个对象永远不会被释放。看看下面的例子。
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
__block MyViewController* myViewController = [[MyViewController] alloc] initWithCompletion:^{
if (indexPath.row == 0) {
[myViewController.navigationController
pushViewController:[[GoodViewController alloc] init]
animated:YES
];
} else if (indexPath.row == 1) {
[myViewController.navigationController
pushViewController:[[BadViewController alloc] init]
animated:YES
];
}
}];
}
一切都很好,除了myViewController的dealloc从导航堆栈弹出时永远不会被调用。当我删除__block时,dealloc最终会被调用,但这样做会阻止我在myViewController的完成块中访问它。我如何在没有内存泄漏的情况下访问其自身完成块内的对象?
你的视图控制器保留了block, block保留了myViewController
,创建了一个retain循环
这样想……视图控制器在block周围放了一个皮带来防止它在视图控制器完成之前被释放。block在视图控制器周围放了一个皮带,以防止它在block完成之前释放内存。
双方都不会释放对方的引用,直到对方先释放它的引用…这是一个保留循环。
有许多方法可以避免保留循环,这取决于您如何使用块。
在你的情况下,我会有完成块传递回视图控制器的块是附加的。这将很容易避免需要保留对象本身。
MyViewController* myViewController = [[MyViewController] alloc]
initWithCompletion:^(MyViewController *viewController) {
if (indexPath.row == 0) {
[viewController.navigationController
pushViewController:[[GoodViewController alloc] init]
animated:YES
];
} else if (indexPath.row == 1) {
[viewController.navigationController
pushViewController:[[BadViewController alloc] init]
animated:YES
];
}
}];
在你的MyViewController类中,你只需用…
if (_completionBlock) _completionBlock(self);
当你设计一个界面时,尽量使它易于使用,不要对用户施加过多的限制。在本例中,通过将控制器作为参数传递回块,您可以让用户编写代码,而不必担心在控制器和块之间创建的保留周期。
使用__weak代替__block,但在初始化后设置块,并在块内通过弱引用访问
MyViewController* myViewController = [[MyViewController] alloc] init];
__weak typeof(myViewController) weakVC = myViewController;
myViewController.completion = ^{
if (indexPath.row == 0) {
[weakVC
pushViewController:[[GoodViewController alloc] init]
animated:YES
];
} else if (indexPath.row == 1) {
[weakVC
pushViewController:[[BadViewController alloc] init]
animated:YES
];
}
};
在这种情况下,你真的必须考虑ARC是怎么做的,以确保所有的东西都像预期的那样发布!
init
方法不应该是异步的。也许这就是你所寻求的方法。
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
MyViewController *myViewController = [[MyViewController] alloc] init];
__weak MyViewController weakMyViewController = myViewController;
myViewController.navigationBlock = ^{
UIViewController *viewControllerToPush;
if (indexPath.row == 0) {
viewControllerToPush = [[GoodViewController alloc] init];
} else if (indexPath.row == 1) {
viewControllerToPush = [[BadViewController alloc] init];
}
[weakMyViewController.navigationController
pushViewController:viewControllerToPush
animated:YES
];
};
//myViewController* goes out of scope here... You probably should keep a reference to it somehow
}
有一个保留周期,因为视图控制器可能保留完成块(我们看不到代码,所以我们不能确定;(但最有可能的是),并且块保留视图控制器,因为块捕获变量myViewController
。
要在没有保留周期的ARC中做到这一点,您需要该块捕获对视图控制器的弱引用。然而,你还必须至少有一个对视图控制器的强引用,否则它将立即有资格被释放。因此,您需要两个变量,一个是弱变量,由块捕获(和__block
,因为它的值将在块创建后设置,但需要由块使用),另一个是强变量:
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
MyViewController* myViewController;
__block __weak MyViewController* myViewControllerWeak;
myViewControllerWeak = myViewController = [[MyViewController] alloc] initWithCompletion:^{
if (indexPath.row == 0) {
[myViewControllerWeak.navigationController
pushViewController:[[GoodViewController alloc] init]
animated:YES
];
} else if (indexPath.row == 1) {
[myViewControllerWeak.navigationController
pushViewController:[[BadViewController alloc] init]
animated:YES
];
}
}];
}
不需要改变类的API就可以修复这个问题。