如何避免__block和completion块的内存泄漏



我遇到一种情况,需要用访问该对象的完成块初始化对象。为了使这种访问成为可能,对象被定义为__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就可以修复这个问题。

最新更新