Core-Data executeFetchRequest freezes App in a bac

2020-03-05 12:17发布

问题:

I have a saveMOC which is the direct parent of a mainMOC, and I need for online fetch another tmpMOC in order not to block my UI whilst fetching a ton of records from the Internet.

My app freezes. I could narrow it to this very point:

       fetchedItems = [tmpMOC executeFetchRequest:fetchRequest error:&error];

I tried to enclose this within dispatch_sync, [moc.parentcontext.parentcontext.persistentStoreCoordinator lock/unlock], [whatevermoc performBlockAndWait]...

I also try to fetch the _mainMOC... No way...

I understand that executeFetchRequestis not thread safe, so, how do I lock whatever I need to lock to get sure I am not inserting a double?

Anybody could help?

UPDATE (1) _saveMOC instantiation in AppDelegate:

- (NSManagedObjectContext *)managedObjectContext
{
if (_mainMOC != nil) {
    return _mainMOC;
}

if (_saveMOC == nil) {
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _saveMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_saveMOC setPersistentStoreCoordinator:coordinator];
        [_saveMOC setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
    }
}
if (_mainMOC == nil) {
    _mainMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [_mainMOC setParentContext:_saveMOC];
    [_mainMOC setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveContextChanges:) name:NSManagedObjectContextDidSaveNotification object:_mainMOC];

temporaryMOCcreation in MainViewController:

    NSManagedObjectContext *temporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
temporaryContext.parentContext = _mainMOC;

[temporaryContext performBlock:^{ //AndWait
    NSError *error=nil;
    Feed *feed=(Feed *)[temporaryContext existingObjectWithID:feedID error:&error];

//...
       [RSSParser parseRSSFeedForRequest:req success:^(NSArray *feedItems)
     {
         NSLog(@"[MasterViewController::fetchPosts] inserting %d Posts.../OK", (int)[feedItems count]);
         feed.valid = [NSNumber numberWithBool:YES];
         for(RSSItem *i in feedItems)
         {
             [self createPostInFeed:feed withTitle:i.title withContent:(i.content?i.content:i.itemDescription) withURL:[i.link absoluteString] withDate:(i.pubDate?i.pubDate:[NSDate date]) inMOC:temporaryContext];
         }
         [[NSNotificationCenter defaultCenter] postNotificationName:@"NewPostsFetched" object:f];

Then the freeze happens in createPostInFeed:

 // @synchronized(fetchRequest) { // THIS DOESN'T CHANGE ANYTHING
 // [moc.persistentStoreCoordinator lock]; // THIS NEITHER
 NSArray *fetchedItems;
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *ent = [NSEntityDescription entityForName:@"Post" inManagedObjectContext:moc];
    fetchRequest.entity = ent;

    fetchRequest.propertiesToFetch = [NSArray arrayWithObjects:@"title", @"url", @"feed.name", nil];
    [fetchRequest setResultType:NSDictionaryResultType];
    fetchRequest.predicate = [NSPredicate predicateWithFormat:@"title == %@ AND url == %@ AND feed.rss == %@", title, url, feed.rss];
    NSError *error = nil;
    fetchedItems = [moc executeFetchRequest:fetchRequest error:&error]; // FREEZES HERE
// [moc.persistentStoreCoordinator unlock]; // THIS DOESN'T CHANGE ANYTHING
// } // Synchronized neither...

Update (2): Here's what the Time Profiler sees.

Update (3): Block edited:

NSUInteger fetchedItems;
NSString *feedRSS=feed.rss;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *ent = [NSEntityDescription entityForName:@"Post" inManagedObjectContext:moc];
fetchRequest.entity = ent;

fetchRequest.predicate = [NSPredicate predicateWithFormat:@"title == %@ AND url == %@ AND feed.rss == %@", title, url, feedRSS];
NSLog(@"MainViewController::createPostInFeed> Before fetchRequest for %@ (%@)", title, url);
NSError *error = nil;
fetchedItems = [moc countForFetchRequest:fetchRequest error:&error];
NSLog(@"MainViewController::createPostInFeed> After fetchRequest for %@ (%@)", title, url); // THIS NSLOGGING NEVER HAPPENS

Update (4): New profiler pic with call tree inverted.

回答1:

What is your goal with createPostInFeed? You show that you are doing a fetch but what do you do with that fetch? Is this a "insert or update" check? or is it just a "insert or skip" test?

Any fetch is going to lock the NSPersistentStoreCoordinator and cause your application to potentially lock up while the fetch is being performed. There are ways to mitigate that:

  1. Run Instruments
  2. Make your fetches more efficient
  3. If you don't need objects (in a insert or skip test) then do a count instead
  4. Fetch on a background queue and make sure your main MOC has all the objects it needs to avoid a lock

What does your instruments profile show you?

What does createPostInFeed do with the results of that fetch?