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

时间:2014-05-09 19:06:37

标签: ios objective-c cocoa-touch memory-leaks objective-c-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。如何在没有内存泄漏的情况下访问其自己的完成块中的对象?

4 个答案:

答案 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的情况下解决问题。