ObjC:ARC过早发布动态创建的视图控制器

时间:2013-07-14 17:13:15

标签: ios objective-c uiviewcontroller automatic-ref-counting

我有一个名为AllThingsViewController的视图控制器,它动态创建名为ThingViewController的其他视图控制器,并将其顶级视图添加到UIScrollView。 (我正在编写专有代码,所以我改变了我的类的名称,但我的代码结构完全相同。)

这是loadView方法包含的内容:

NSArray *things = [[ThingDataController shared] getThings];

if ([things count] == 0) {
    // code in this block is not relevant as it's not being executed...
} else {

    for(unsigned int i = 0; i < [things count]; ++i) {
        ThingViewController *thingViewController = [[ThingViewController alloc] init];
        [thingViewController loadView];

        [scrollView addSubview:thingViewController.topView];
        thingViewController.topView.frame = CGRectNewOrigin(thingViewController.topView.frame,
                0, thingViewController.topView.frame.size.height*i);

        [thingViewController displayThing:thing[i]];
    }
}

ThingViewController的loadView方法如下所示:

- (void)loadView
{
    NSArray *topLevelObjs = nil;
    topLevelObjs = [[NSBundle mainBundle] loadNibNamed:@"ThingView" owner:self options:nil];
    if (topLevelObjs == nil)
    {
        NSLog(@"Error: Could not load ThingView xib\n");
        return;
    }
}

当我的应用程序启动时,一切都正确显示,直到我尝试点击ThingViewController加载的xib中存在的其中一个按钮,此时由于异常而崩溃:“无法识别的选择器发送到实例”。似乎ARC太早发布了我的ThingViewController实例。

查看我的代码,我认为这是因为它们没有被保留,所以我在我的AllThingsViewController类中创建了一个NSMutableArray作为实例变量,并开始向它添加ThingViewControllers:

NSArray *things = [[ThingDataController shared] getThings];

if ([things count] == 0) {
    // not being executed...
} else {

    for(unsigned int i = 0; i < [things count]; ++i) {
        ThingViewController *thingViewController = [[ThingViewController alloc] init];
        [thingViewController loadView];

        [scrollView addSubview:thingViewController.topView];
        thingViewController.topView.frame = CGRectNewOrigin(thingViewController.topView.frame,
                0, thingViewController.topView.frame.size.height*i);

        [thingViewController displayThing:thing[i]];

        [allThingsViewControllers addObject:thingViewController];
    }
}

但是,它并没有改变任何东西,即使这些对象被添加到数组中。最后,为了确认这是ARC早期发布它,我将“thingViewController”更改为AllThingsViewController中的实例变量并更改:

ThingViewController *thingViewController = [[ThingViewController alloc] init];

是:

thingViewController = [[ThingViewController alloc] init];

果然,当我点击它的按钮时,可滚动列表中的最后一项不会崩溃,但是其他项会这样做,因为它的ThingViewController没有被释放。

我还是相对较新的ARC,但经过一堆谷歌搜索我不知道如何解决这个问题。我该怎么办?

2 个答案:

答案 0 :(得分:3)

一些事情。

问题1:

这看起来就像是你的错误的原因:

[allBillViewControllers addObject:billViewController];

应该是:

[allBillViewControllers addObject:thingViewController];

右?

问题2

您没有将视图控制器正确添加到视图层次结构中。它应该是这样的:

[self addChildViewController:childViewController];
[childViewController.view setFrame:targetFrame];
[scrollView addSubview:childViewController.view];
[childViewController didMoveToParentViewController:self];

同样在删除子视图控制器时:

[childViewController willMoveToParentViewController:nil];
[childViewController.view removeFromSuperview];
[childViewController removeFromParentViewController];

问题3

永远不要在视图控制器上显式调用loadView。每当您访问视图控制器的view属性时,UIKit都会调用它。

问题4

您必须将子视图控制器的view添加到滚动视图,而不是其视图层次结构中的任意子视图topView。重构你的ThingViewController类,让自己更简单。 : - )

答案 1 :(得分:2)

让我们来看看你的代码:

for(unsigned int i = 0; i < [things count]; ++i) {
    ThingViewController *thingViewController = [[ThingViewController alloc] init];
    [thingViewController loadView];

    [scrollView addSubview:thingViewController.topView];
    thingViewController.topView.frame = CGRectNewOrigin(thingViewController.topView.frame,
            0, thingViewController.topView.frame.size.height*i);

    [billViewController displayThing:thing[i]];

    [allBillViewControllers addObject:billViewController];
}

执行for循环的每个循环后,没有任何内容会对ThingViewController有强引用。因此,它被释放并被摧毁。

如果ThingViewControllerUIViewController的子类,那么它应该成为scrollview视图控制器的“子视图控制器”。我建议阅读the section on from the View Controller Programming Guide on creating custom container view controllers(即封装并显示其他视图控制器的视图控制器)。