NSFetchedResultsController在storyboard中保留对deallocated delegate controller的引用,导致崩溃

时间:2012-02-09 16:55:08

标签: objective-c memory-leaks ios5 nsfetchedresultscontroller uistoryboard

我有一个简单的UIViewTable,在故事板中使用UINavigationController push segue实现了深入细节。 它不时发生,表视图控制器似乎被解除分配,而我在详细视图中,因此我得到了着名的:

[MyViewController controllerWillChangeContent:]: message sent to deallocated instance

我解释得更好,我有一个NSOperation队列,它以异步方式加载我的数据,并在完成后立即填充表。正确检索数据并填充表格。 对于详细视图,我在prepareForSegue方法中单击一个单元格并将NSManagedObjectID传递给目标控制器。当我对详细信息视图进行更改时,非常随机,取出的控制器松开其委托,或者看起来,作为控制器的委托本身被取消分配。导致崩溃。

将获取的结果控制器声明为属性:

@property(nonatomic,strong) NSFetchedResultsController *fetchedResultsController;

然后这就是从viewDidLoad开始一切正常工作的方式。

- (void)viewDidLoad {
    [super viewDidLoad];
    [self loadDataAsynchronously];
}

-(void)loadDataAsynchronously {

    NSOperationQueue *queue = [NSOperationQueue new];
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
                                                                            selector:@selector(loadData)
                                                                              object:nil];

    [queue addOperation:operation];
}


-(void)loadData {

    NSFetchRequest *findAllEntities = [[NSFetchRequest alloc] init];
    [findAllEntities setEntity:ENTITY_DESC];

    NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"created" ascending:YES];
    [findAllEntities setSortDescriptors:[NSArray arrayWithObject:sort]];
    [findAllEntities setFetchBatchSize:20];

    [NSFetchedResultsController deleteCacheWithName:@"MyCache"];
    if(self.fetchedResultsController==nil) {
        self.fetchedResultsController = [[NSFetchedResultsController alloc] 
                                            initWithFetchRequest:findAllPlants
                                            managedObjectContext:MOC
                                            sectionNameKeyPath:nil 
                                            cacheName:@"MyCache"];

        self.fetchedResultsController.delegate=self;
    }
    NSError *error=nil;
    if (![FRC performFetch:&error]) {
        exit(EXIT_FAILURE);
    }

    [self.dataTableView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES];    

}

此代码有效,并且大部分时间也在详细信息视图中工作,该视图称为segue,如下所示:

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

    IP2SegueIdentifier segueIdentifier = [IP2Factory segueSolver:[segue identifier]];  
    MyDestinationViewController *dvc = [segue destinationViewController];
    NSIndexPath *indexPath = [TV indexPathForSelectedRow];
    dvc.entityID=[[self.fetchedResultsController objectAtIndexPath: indexPath] objectID];

}

并且目标控制器正确获取实体id并通过询问上下文来重构对象。 然后,当我在详细视图控制器中时,我可以对实体进行更改,当我返回导航层次结构时,我会进行上下文保存。 正是在这一点上,应用程序崩溃,正好在上下文保存时。不是经常,但不时。 因为获取的结果控制器识别更改并提交给已取消分配的委托。

我现在几乎没有怀疑,我使用iOS 5和ARC,因此编译器应该(几乎)完全控制release和dealloc方法。我还使用了一个带有简单导航层次结构的故事板,它可以保证整个以前的视图控制器链得到保留。

我还运行探测器进行内存泄漏/僵尸分析,但无法发现任何错误,相反我很高兴所有对象管理都很好。

此时我没有多少猜测,所以请随意指出我可能忘记检查的内容,或者我在代码中看错的内容。

谢谢

1 个答案:

答案 0 :(得分:21)

首先,关于ARC的说明。虽然ARC提供自动归零weak指针,但它不会使assign指针自动归零。 NSFetchResultsController为其代理使用assign属性(请参阅NSFetchedResultsController.h):

@property(nonatomic, assign) id< NSFetchedResultsControllerDelegate > delegate;

在解除分配之前,您仍有责任将自己清除为代表。您通常在dealloc

中执行此操作
- (void)dealloc {
  _fetchedResultsController.delegate = nil;
}

您可能还想删除fetchedResultsController中的viewWillDisappear:(包括将自己移除为代理人)。通常,当您在屏幕外时,您不希望抓取请求保持不变。 (如果这样做,您可能应该管理模型对象而不是视图控制器中的提取,因为视图控制器可以在其视图在屏幕外的任何时候消失。)

您的loadData很奇怪,因为它会创建findAllEntities,但实际上会使用findAllPlants。这是一个错字还是一个错误?如果在ivar中有单独的findAllPlants获取请求,这也可能是您遇到问题的原因。