[英]How to avoid a memory leak with __block and completion blocks
我有一种情况,需要使用访问所述对象的完成块来初始化对象。 为了使这种访问成为可能,该对象被定义为__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。 如何在没有内存泄漏的情况下访问其自己的完成块中的对象?
您的视图控制器正在保留块,并且块保留myViewController
,从而创建保留周期。
想象一下......视图控制器在块周围放置一条皮带,以防止它解除分配,直到视图控制器完成。 该块在视图控制器周围放置一条带,以防止解除分配,直到块完成。
在另一个首次发布其引用之前,它们都不会释放对另一个的引用...因此保留周期。
有许多方法可以避免保留周期,具体取决于您使用块的方式。
在你的情况下,我会让完成块传回块附加到的视图控制器。 这将很容易避免保留对象本身的需要。
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);
设计界面时,尽量使其易于使用,而不对用户施加不适当的限制。 在这种情况下,通过将控制器作为参数传递回块,您可以让用户编写代码,而不必担心控制器和块之间创建的保留周期。
而不是__block使用__weak但是在初始化之后设置块并且通过弱引用在块内访问。
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的情况下解决问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.