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

Leo*_*rdo 4 memory-leaks objective-c nsfetchedresultscontroller ios5 uistoryboard

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

[MyViewController controllerWillChangeContent:]: message sent to deallocated instance
Run Code Online (Sandbox Code Playgroud)

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

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

@property(nonatomic,strong) NSFetchedResultsController *fetchedResultsController;
Run Code Online (Sandbox Code Playgroud)

然后这就是从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];    

}
Run Code Online (Sandbox Code Playgroud)

这段代码有效,大部分时间也在详细信息视图中工作,这个视图被称为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];

}
Run Code Online (Sandbox Code Playgroud)

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

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

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

我在这一点上没有多少猜测,所以请随意指出我可能忘记检查的内容,或者我在代码中看到错误的内容.

谢谢

Rob*_*ier 21

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

@property(nonatomic, assign) id< NSFetchedResultsControllerDelegate > delegate;
Run Code Online (Sandbox Code Playgroud)

在您解除分配之前,您仍有责任将自己清除为代表.您通常这样做dealloc:

- (void)dealloc {
  _fetchedResultsController.delegate = nil;
}
Run Code Online (Sandbox Code Playgroud)

你也可能想摆脱你的fetchedResultsControllerviewWillDisappear:(包括移除自己作为委托).通常,当您在屏幕外时,您不希望抓取请求保持不变.(如果这样做,您可能应该管理模型对象而不是视图控制器中的提取,因为视图控制器可以在其视图在屏幕外的任何时候消失.)

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