繁体   English   中英

如何使用__block和完成块来避免内存泄漏

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM