iOS - 使用 CoreData 的 dispatch_async 保留周期

Posted

技术标签:

【中文标题】iOS - 使用 CoreData 的 dispatch_async 保留周期【英文标题】:iOS - dispatch_async retain cycle with CoreData 【发布时间】:2013-12-11 16:14:34 【问题描述】:

我是 dispatch_queue 的新手,在后台保存到 CoreData 时遇到了问题。我已阅读 CoreData 编程指南,并且在后台线程中创建了一个单独的 NSManagedObjectContext。当我在测试项目中执行一个简单的循环来创建NSManagedObjects 时,我没有任何问题,创建了对象,我使用NSManageObjectContextDidSaveNotification 将更改传达给主线程。

我相信我的问题在于我对 GCD 的无知。我正在解析 XML,在 parserDidEndDocument: 中,我需要将数据保存到 CoreData 而不会阻塞 UI。每当使用这个块时,我的应用程序内存开始不受控制地滚雪球,直到最后我得到Terminated app due to memory pressure

注意事项:我使用 AppDelegate 的单例来保存我的 NSPersistentStoreCoordinator,而 stuffToSave 是由我的解析器创建的 NSMutablearray

任何方向都将不胜感激。这两天我一直在打我的头!

-(void)parserDidEndDocument:(NSXMLParser *)parser

dispatch_queue_t backgroundQ = dispatch_queue_create("com.example.myapp", NULL);

__block AppDelegate *app= [[UIApplication sharedApplication]delegate];
__block NSMutableArray *array = self.stuffToSave;


dispatch_async(backgroundQ, ^(void)

    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
    context.persistentStoreCoordinator = [app persistentStoreCoordinator];

    HNField *field = [HNField fieldWithField_id:[NSNumber numberWithInt:0] inContext:context];
    //initalize array if needed
    if (!field.arrayOfPolylines) field.arrayOfPolylines = [[NSMutableArray alloc]init];

    //add polyline to array to save in database
    for (id obj in array) 
        if ([obj isKindOfClass:[HNPolyline class]]) 
            HNPolyline *theLine = (HNPolyline *)obj;
            [field.arrayOfPolylines addObject:theLine];
        else if ([obj isKindOfClass:[HNParserPoint class]])
            HNPoint *point = [HNPoint createAPointWithContext:context];
            HNParserPoint *pPoint = (HNParserPoint *)obj;
            point.point_id = pPoint.point_id;
            point.lat = pPoint.lat;
            point.lng = pPoint.lng;
            point.yield = pPoint.yield;
            point.farm_id = self.farm_id;
            point.field_id = self.field_id;
            point.inField = field;
            //add every point in database
            [field addFieldPointsObject:point];
        
    
    NSError *error;
    [context save:&error];

       );



self.stuffToSave = nil;
self.parser = nil;

编辑 1: 我正在从与我正在解析的地方不同的班级收听NSManageObjectContextDidSaveNotification。在viewDidLoad 我有:

// observe the ParseOperation's save operation with its managed object context
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(didSave:)
                                             name:NSManagedObjectContextDidSaveNotification
                                           object:nil];

然后我使用 Apple 的“ThreadedCoreData”示例中的以下内容。

-(void)didSave:(NSNotification *)notification

    if (notification.object != [self.app managedObjectContext]) 
        NSLog(@"not main context");
        [self performSelectorOnMainThread:@selector(updateMainContext:) withObject:notification waitUntilDone:NO];
    else
         NSLog(@"b Thread: %@",[NSThread currentThread]);
       NSLog(@"main context");
    


// merge changes to main context
- (void)updateMainContext:(NSNotification *)notification 

    assert([NSThread isMainThread]);
    [[self.app managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
    NSLog(@"did save");

【问题讨论】:

您在使用 ARC 吗?如果没有,您正在泄漏队列。此外,我会创建一个 ivar 或一个属性来管理队列。 另外你要插入多少数据? 是的,我正在使用 ARC。您能否详细说明“管理队列”?在解析“stuffToSave”结束时,其中通常有 30-50 个对象。谢谢! “HNField”和“HNPoint”也是 NSManagedObject 的子类。 您应该将队列存储为类的属性。定义方法的那个。50个对象就可以了。或许还牵扯到别的东西。你能补充一些其他的细节吗? 【参考方案1】:

也许我无法正确理解,但从我所看到的情况来看,我会避免一些事情。

我可能错了,但我认为问题在于您正在后台队列上启动异步操作,但您最终使用的是默认上下文队列,您保证它是线程安全的,但事实并非如此。因此,当您保存时,您会发送通知,这会触发保存等,直到您填满内存。要验证这一点,请尝试在 updateMainContext 中放置一个断点,并查看它被调用了多少次。

如果您使用不同的上下文,您应该使用 NSPrivateQueueConcurrencyType 对其进行初始化,这样您就可以确保所有工作都在单独的线程中完成。所以我会像这样重新访问dispatch async,在这种情况下你可以这样做代替dispatch_async:

NSManagedObjectContext *privateCtx = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 

[privateCtx performBlock:^() 
  // put the code you have in dispatch async...
];

然后在另一个类上,您应该找到一种仅侦听私有上下文的方法,当然您需要参考。如果你不能,我想避免来自主线程的任何东西是可以的,如果该类只是从后台合并的一个点。

【讨论】:

【参考方案2】:

当我发布我正在使用谷歌地图 SDK (1.6.1.6332) 并且谷歌地图 SDK 也使用核心数据时,我没有提及或意识到。我在我的应用程序委托中观察NSManagedObjectContextDidSaveNotification,但未能过滤通知。因此,谷歌地图发送了NSManagedObjectContextDidSaveNotification,而我的应用程序试图合并这些更改,这是我的问题的根源。

我不知道这是否是最好的解决方案,但适用于我的目的。因为我在我的应用程序委托中测试通知,所以我只有一个对我的主 MOC 的引用,所以我用它来确保通知不是来自它,然后我通过测试来测试通知是否来自感兴趣的类通知的字符串描述(其中包含需要合并的感兴趣类的多个实例)。这样做会阻止谷歌地图尝试将其更改与我的模型合并。支持@Leonardo 的意见,最终得出答案。

- (void)mergeChanges:(NSNotification *)notification 

    NSLog(@"saved changes called");
    if (notification.object != self.managedObjectContext) 
        NSLog(@"call was not main MOC");
        NSString *testingString = notification.description;
        if ([testingString rangeOfString:@"HNDemoResponse"].location != NSNotFound) 
            [self performSelectorOnMainThread:@selector(updateMainContext:) withObject:notification waitUntilDone:YES];
        else NSLog(@"call was goole maps");
    

【讨论】:

以上是关于iOS - 使用 CoreData 的 dispatch_async 保留周期的主要内容,如果未能解决你的问题,请参考以下文章

iOS CoreData

IOS CoreData的(增删查改)

ios开发之CoreData使用

iOS - 使用 CoreData 的 dispatch_async 保留周期

Swift/IOS/CoreData:如何在自动生成的 CoreData 类中将 var 定义为枚举类型?

使用没有版本的版本更新使用 coredata 的 ios 应用程序