2014-02-06 3 views
1

У меня возникли проблемы с подключением приложения к NSFetchRequest. Я читал об этом в течение нескольких часов. По-видимому, проблема с потоками продолжается, но я не могу понять, как ее исправить.NSFetchRequest Hangs

Я загружаю объект JSON с моего сервера, разбирая его и сохраняя его в модели CoreData. Все это происходит в фоновом потоке, который завершен до того, как мое приложение зависает. Вот код, где это происходит:

- (void) populateRegistrationList:(NSString *) list script:(NSString *)script 
{ 

    NSURL *URL = [NSURL URLWithString:[PRIMARY_URL stringByAppendingString:script]]; 
    NSURLRequest *request = [NSURLRequest requestWithURL:URL]; 
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; 
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration]; 
    NSURLSessionDownloadTask *task = [session downloadTaskWithRequest:request 
     completionHandler:^(NSURL *localfile, NSURLResponse *response, NSError *error) { 
      if (!error) { 

       NSArray *jObj = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:localfile] 
                   options:kNilOptions 
                    error:nil]; 
       // make sure that the result is successful 
       if (![[jObj valueForKey:@"result"] isEqualToString:@"success"]) { 
        return; 
       } 

       // get a context pointer to the database model 
       AppDelegate* appDelegate = [UIApplication sharedApplication].delegate; 
       self.managedObjectContext = appDelegate.managedObjectContext; 

       // clear out any data from previous downloads 
       [self dumpTable:list]; 

       NSArray *jArr = (NSArray *) [jObj valueForKey:@"message"]; 

       for (int i=0; i<[jArr count]; i++) { 
        NSDictionary *dict = [jArr objectAtIndex:i]; 
        [self commitToCoreData:dict]; 

        NSError *error; 
        if (![self.managedObjectContext save:&error]) 
         NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]); 
       } 

       // set this user default to YES to tell the RegistrationTwoViewController that we are done 
       [[NSUserDefaults standardUserDefaults] setBool:YES forKey:REGISTRATION_LISTS_RETRIEVED]; 
       [[NSUserDefaults standardUserDefaults] synchronize]; 

      } 
     }]; 

    [task resume]; 
} 

Впоследствии, в основном потоке, я выполнить выборку запроса на доступ к этим данным, как это:

@implementation ChooseSuperGroupViewController 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    // get a context pointer to the database model 
    AppDelegate* appDelegate = [UIApplication sharedApplication].delegate; 
    self.managedObjectContext = appDelegate.managedObjectContext; 

    NSLog(@"About to get fetched objects");  
    [self.managedObjectContext performBlockAndWait:^{ 
     NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"SuperGroups"]; 
     NSError *error; 
     self.fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error]; 
     NSLog(@"Done with fetch request"); 
    }]; 
}   

К сожалению, это висит на self.fetchedObjects линии. Я понимаю, что я использую два разных потока, но я знаю, что фоновый поток завершился, прежде чем я попытаюсь выполнить запрос на выборку. Почему в этот момент я должен заниматься другим вопросом? Я не вижу, как может произойти состояние гонки.

Что мне делать, чтобы исправить это? Могу ли я заставить запрос на выборку работать в том же фоновом потоке? Все сообщения и статьи, которые я прочитал на этом, похоже, являются сложными, или они вообще не затрагивают проблему потоковой передачи. Я застрял и не знаю, что еще делать.

Любая помощь приветствуется. Благодарю.

Это из моего AppDelegate:

- (NSManagedObjectContext *) managedObjectContext { 
    if (_managedObjectContext != nil) { 
     return _managedObjectContext; 
    } 
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 
    if (coordinator != nil) { 
     _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
     [_managedObjectContext setPersistentStoreCoordinator: coordinator]; 
    } 

    return _managedObjectContext; 
} 

- (NSManagedObjectModel *)managedObjectModel { 
    if (_managedObjectModel != nil) { 
     return _managedObjectModel; 
    } 
    _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil]; 

    return _managedObjectModel; 
} 

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { 
    if (_persistentStoreCoordinator != nil) { 
     return _persistentStoreCoordinator; 
    } 
    NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] 
               stringByAppendingPathComponent: @"PhoneBook.sqlite"]]; 
    NSError *error = nil; 
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] 
            initWithManagedObjectModel:[self managedObjectModel]]; 
    if(![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType 
                configuration:nil URL:storeUrl options:nil error:&error]) { 
     /*Error for store creation should be handled in here*/ 
    } 

    return _persistentStoreCoordinator; 
} 

ответ

3

Это действительно не имеет значения, что фоновый поток завершен, проблема в том, что вы не можете безопасно использовать один и тот же контекст, управляемый объект на несколько потоков без обеспечения синхронизация - возможно, используя вызовы GCD, такие как dispatch_async, но предпочтительно с использованием встроенной системы синхронизации Core Data. Я не знаю конкретно, почему это происходит в этом случае, но вы нарушаете основное правило безопасного использования Core Data, поэтому ожидаются плохие результаты.

Чтобы использовать встроенную синхронизацию Core Data, создайте свой контекст, используя NSMainQueueConcurrencyType или NSPrivateQueueConcurrencyType. Затем, всякий раз, когда вы используете этот контекст (или объекты, которые вы извлекли из этого контекста), введите код внутри вызова performBlock или performBlockAndWait. Это гарантирует синхронный доступ, независимо от количества потоков или очередей.

В качестве альтернативы создайте новый контекст управляемого объекта для фонового потока. Нет причин не иметь больше одного. Сделайте свою работу в этом отдельном контексте и послушайте NSManagedObjectContextDidSaveNotification в основном потоке, чтобы объединить новые изменения.

Но что бы вы ни делали, не использовать тот же контекст на нескольких потоках без надлежащих мер предосторожности. Даже когда это работает, это только случайно. В конце концов это не удастся.

+0

спасибо. Я попытался реализовать ваше предложение. В моем AppDelegate я изменил свой MOC init на 'initWithConcurrencyType: NSPrivateQueueConcurrencyType' и затем поместил запрос на выборку в блок. Я все равно получаю одинаковые результаты. Вы видите, что я делаю неправильно? Еще раз спасибо. Я очень ценю совет! – Alex

+0

Вы, кажется, не используете 'performBlock' или' performBlockAndWait' в своем фоновом потоке. –

+0

OK. Я подумал, что, передав сообщение 'self.managedObjectContext', это поместит его в нужную очередь. Очевидно нет. Итак, теперь я встроил 'dispatch_async' внутри этого блока. Это, похоже, устраняет проблему висячего приложения, но теперь мой запрос на выбор возвращается пустым. Я схожу с ума.Я не знаю, как сообщить этому запросу выборки для запуска в той же очереди, в которой был создан MOC. – Alex