我的情况是需要使用访问所述对象的完成块来初始化对象。为了使这种访问成为可能,该对象被定义为__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。如何在没有内存泄漏的情况下访问其自己的完成块中的对象?
答案 0 :(得分:3)
您的视图控制器正在保留该块,并且该块保留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);
设计界面时,尽量使其易于使用,而不对用户施加不适当的限制。在这种情况下,通过将控制器作为参数传递回块,您可以让用户编写代码,而不必担心控制器和块之间创建的保留周期。
答案 1 :(得分:1)
使用__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进行的操作,以确保所有内容都按预期发布!
答案 2 :(得分:0)
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
}
答案 3 :(得分:0)
有一个保留周期,因为视图控制器可能会保留完成块(我们不会看到代码,因此我们无法确定;但很可能确实如此),并且块保留视图控制器,因为块捕获变量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的情况下解决问题。