核心数据:在多线程 iOS 应用程序中实现多个 NSManagedObjects 和 NSFetchedResultsController 非常困惑

Posted

技术标签:

【中文标题】核心数据:在多线程 iOS 应用程序中实现多个 NSManagedObjects 和 NSFetchedResultsController 非常困惑【英文标题】:Core-Data: Very confused by the implementation of multiple NSManagedObjects and an NSFetchedResultsController in a multithreading iOS app 【发布时间】:2013-10-08 18:59:38 【问题描述】:

我有一个例程在后台获取 RSS 条目,如果还没有的话,将它们插入到我的 NSManagedObjectContext 中。

我的问题是这个对象找不到重复或崩溃,这取决于NSManagedObjectContext I use...请帮助我。

这里是简化的.h

    @interface AsyncFetchEngine : NSObject <NSXMLParserDelegate,NSFetchedResultsControllerDelegate>
    @property (strong, nonatomic) dispatch_queue_t rssParserQueue;
    @property (strong, nonatomic) NSMutableArray *uRLsToFetch;
    @property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
    @property (strong, nonatomic) NSManagedObjectContext *childManagedObjectContext;
    @property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;

    - (Boolean) isAlreadyInTheFetchQueue:(Feed *)feed;
    - (void) fetchPosts:(Feed *)feed;
    - (void) createPostInFeed:(Feed*)feed withTitle:(NSString *)title withContent:(NSString *)content withURL:(NSString *)url withDate:(NSDate *)date;

现在是 init 方法: 请注意,如果我在这里设置 _childManagedObjectContext 的父级,程序会崩溃。

    -(AsyncFetchEngine *)init
    
        _rssParserQueue = dispatch_queue_create("com.example.MyQueue", NULL);
        _uRLsToFetch = [[NSMutableArray alloc] initWithCapacity:32];
        _childManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_childManagedObjectContext setPersistentStoreCoordinator:[_managedObjectContext persistentStoreCoordinator]];

        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
        [center addObserver:self
                   selector:@selector(contextDidSave:)
                       name:NSManagedObjectContextDidSaveNotification
                     object:_childManagedObjectContext];
        return self;
    
    - (void)contextDidSave:(NSNotification*)notification
    
        void (^mergeChanges) (void) = ^ 
            [_managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
        ;
        if ([NSThread isMainThread]) 
            mergeChanges();
         else 
            dispatch_sync(dispatch_get_main_queue(), mergeChanges);
        
    

获取方法: 注意:不确定使用哪个 MOC 来确定 localFeed,_managedObjectContext 会使应用程序崩溃。

    - (void) fetchPosts:(Feed *)feed
    
        if (!_childManagedObjectContext.parentContext) 
            [_childManagedObjectContext setParentContext:self.managedObjectContext];
        

        if ([self isAlreadyInTheFetchQueue:feed]) 
            NSLog(@"AsyncFetchEngine::fetchPosts> \"%@\" is already in the fetch queue", feed.name);
            return;
        
        [_uRLsToFetch addObject:feed];
        NSURL *url=[NSURL URLWithString:feed.rss];
        NSURLRequest *req = [[NSURLRequest alloc] initWithURL:url];
        if (![NSURLConnection canHandleRequest:req]) 
            return;
        

        Feed *localFeed = ((Feed *)[_childManagedObjectContext existingObjectWithID:[feed objectID] error:nil]);

        dispatch_async(_rssParserQueue, ^
            NSLog(@"AsyncFetchEngine::fetchPosts> Opening %@", feed.rss);

            [RSSParser parseRSSFeedForRequest:req success:^(NSArray *feedItems)
             
                 for(RSSItem *i in feedItems)
                 
                     [self createPostInFeed:localFeed withTitle:i.title withContent:(i.content?i.content:i.itemDescription) withURL:[i.link absoluteString] withDate:(i.pubDate?i.pubDate:[NSDate date])];
                 
                 NSLog(@"AsyncFetchEngine::fetchPosts> Found %d items", [feedItems count]);
                 [_uRLsToFetch removeObject:feed];
             
                                      failure:^(NSError *error)
             
                 NSLog(@"AsyncFetchEngine::fetchPosts> RSSParser lost it: %@", [error localizedDescription]);
             ];
        );
    

    - (Boolean) isAlreadyInTheFetchQueue:(Feed *)feed
    
        Feed *f=nil;
        for (f in _uRLsToFetch) 
            if ([f isEqual:feed])
                return YES;
            
        
        return NO;
    

NSFecthedResultsController,我需要这样吗?

    - (NSFetchedResultsController *)fetchedResultsController
    
        if (_fetchedResultsController != nil) 
            return _fetchedResultsController;
        

        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        // Edit the entity name as appropriate.

    // Note: I originally tried to use the main MOC here, but it also used to crash the app.

        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Post" inManagedObjectContext:_childManagedObjectContext];
        [fetchRequest setEntity:entity];

        // Set the batch size to a suitable number.
        [fetchRequest setFetchBatchSize:20];

        // Edit the sort key as appropriate.
        NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO];
        NSArray *sortDescriptors = @[sortDescriptor];

        [fetchRequest setSortDescriptors:sortDescriptors];
        /* NSPredicate *predicate =[NSPredicate predicateWithFormat:@"feed.rss LIKE '%@'",  _detailItem.rss];
         [fetchRequest setPredicate:predicate]; */

        // Edit the section name key path and cache name if appropriate.
        // nil for section name key path means "no sections".
        NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
        aFetchedResultsController.delegate = self;
        _fetchedResultsController = aFetchedResultsController;

        return _fetchedResultsController;
    

根据转储堆栈,它通常在此方法的某个地方崩溃。 如果它没有崩溃,我会将我的帖子添加到(空)类别...

    - (void)createPostInFeed:(Feed*)feed withTitle:(NSString *)title withContent:(NSString *)content withURL:(NSString *)url withDate:(NSDate *)date
    
        [_childManagedObjectContext performBlockAndWait:^
            NSError *error = nil;
            if (![self.fetchedResultsController performFetch:&error]) 
                NSLog(@"Error in refetch: %@",[error localizedDescription]);
                abort();
            
        ];
        NSLog(@"AsyncFetchEngine.h: Searching similar posts among %d", [[self.fetchedResultsController fetchedObjects] count]);
        Boolean found=NO;
        NSPredicate *predicate=[NSPredicate predicateWithFormat:@"title == %@ AND url == %@ AND feed == %@", title, url, feed];
        NSArray *similarPosts = [_fetchedResultsController.fetchedObjects filteredArrayUsingPredicate:predicate];

        if ([similarPosts count] > 0)
        
            NSLog(@"\n\n\n\t\tAsyncFetchEngine::fetchPosts> Skipping %@ (%@)", title, url);
         else 
            NSLog(@"\n\n\n\t\tAsyncFetchEngine::fetchPosts> Putting new post in %@", feed.name);
            NSEntityDescription *postEntityDescription = [NSEntityDescription entityForName:@"Post"
                                                                         inManagedObjectContext:_childManagedObjectContext];
            [_childManagedObjectContext performBlock:^
                Post *initPost = (Post *)[[NSManagedObject alloc]
                                          initWithEntity:postEntityDescription
                                          insertIntoManagedObjectContext:_childManagedObjectContext];
                initPost.title = title;
                initPost.url = url;
                initPost.excerpt = content;
                initPost.date = date;
                initPost.read = nil;
                initPost.feed = feed;

                NSError *error;
                if (![_childManagedObjectContext save:&error])
                
                    NSLog(@"[createPost] Error saving context: %@", error);
                
                NSLog(@"Created: %@ (%@)", title, url);
                //[[NSNotificationCenter defaultCenter] postNotificationName:@"NewPostAdded" object:self];
            ];

        
    

所以,我的问题是:

当我有多个 MOC 时,我是否从 main 中获取并写入一个 child? 我应该如何分别使用以上这些?

谢谢!

【问题讨论】:

【参考方案1】:

为了最终帮助另一个新手而回答我自己的问题。

使用 Coredata 检查 NSOperationQueue。 有一个很好的例子here。

【讨论】:

以上是关于核心数据:在多线程 iOS 应用程序中实现多个 NSManagedObjects 和 NSFetchedResultsController 非常困惑的主要内容,如果未能解决你的问题,请参考以下文章

核心数据多线程问题

在多线程或并发中控制事务的解决方案

如何在Java中实现多个线程来下载单个表数据?

如何在Java中实现多个线程来下载单个表数据?

使用跨越多个实体的核心数据绑定在 Cocoa 中实现父->子向下钻取

多线程如何在 C 中实现?