2013-06-04 3 views
1

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

Скажем, в итоге я получил 10 объектов Bird в Core Data. Каждая птица имеет имя, описание и т. Д. И отношение «многие» с BirdImage, которое является его собственной сущностью. BirdImage имеет атрибут «image_url» (строка) и атрибут «изображение» (Transformable).

Теперь, когда я добираюсь до экрана в приложении, в котором будут отображаться фотографии птиц, я сначала проверяю атрибут «изображение» BirdImage. Если это не null, я просто устанавливаю anyBirdEntity.image как образ UIImageView. Если он равен нулю, мне нужно загрузить изображение. В коде, который выглядит следующим образом:

@property (nonatomic, strong) AssetRequest *assetRequest; //this is just a wrapper for an asset url, cache policy, and time out 
@property (nonatomic, strong) NSURLRequest *assetURLRequest; 
@property (nonatomic, strong) NSURLConnection *assetConnection; 
@property (nonatomic, strong) NSMutableData *assetConnectionData; 
@property (nonatomic, strong) BirdImage *imageEntity; 
@property (nonatomic, strong) NSManagedObjectContext *objectContext; 

... 

- (void)load { 

    dispatch_async(dispatchQueue, ^{ 

     //Check for the image in Core Data 

     self.objectContext = [[NSManagedObjectContext alloc] 
           initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
     self.objectContext.parentContext = [[CoreDataController sharedController] managedObjectContext]; 


     NSFetchRequest *fetch = [NSFetchRequest fetchRequestWithEntityName:@"BirdImage"]; 
     NSPredicate *predicate = [NSPredicate predicateWithFormat:@"image_url = %@", [self.assetRequest.assetURL absoluteString]]; 
     [fetch setPredicate:predicate]; 

     NSArray *objects = [self.objectContext executeFetchRequest:fetch error:nil]; 

     if ([objects count] > 0) 
     { 
      BirdImage *birdImage = [objects objectAtIndex:0]; 
      if (birdImage.image) { 
       dispatch_async(dispatch_get_main_queue(), ^{ 
        BirdAsset *asset = [[BirdAsset alloc] init]; 

        asset.url = [NSURL URLWithString:birdImage.image_url]; 
        asset.image = birdImage.image; 
        if (self.successBlock) 
         self.successBlock(asset); //the caller will use asset.image for the UIImageView 
       }); 

       return; 
      }else{ 
       //no image found, need to download it 
       self.imageEntity = birdImage; //this is the entity I want to re-save in Core Data once the image finishes downloading 

       dispatch_async(dispatch_get_main_queue(), ^{ 

        self.assetURLRequest = [NSURLRequest requestWithURL:self.assetRequest.assetURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:self.assetRequest.timeOut]; 

        self.assetConnection = [[NSURLConnection alloc] initWithRequest:self.assetURLRequest delegate:self startImmediately:NO]; 
        [self.assetConnection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 
        [self.assetConnection start]; 

       }); 
      } 

     } 

     }]; 


    });  
} 

Затем, когда загрузка завершается:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection { 

    dispatch_async(dispatchQueue, ^{ 

     UIImage *resultImage = [UIImage decompressImageFromData:self.assetConnectionData]; 
     NSData *resultData = UIImagePNGRepresentation(resultImage); 

     DLog(@"saving to core data: %@", self.imageEntity.image_url); //THIS HAPPENS 10 TIMES (every time) 

     self.imageEntity.image = resultImage; 

      @try { 
       NSError *saveError = nil; 
       if (![self.objectContext save:&saveError]) 
        NSLog(@"saveError %@", saveError); 
      } 
      @catch (NSException *exception) { 
       NSLog(@"Exception: %@", exception); 
      } 


      [[CoreDataController sharedController] saveContext]; 


      BirdAsset *finalAsset = [[BirdAsset alloc] init]; 
      finalAsset.data = resultData; 
      finalAsset.image = resultImage; 
      finalAsset.url = [NSURL URLWithString:self.imageEntity.image_url]; 


      DLog(@"SUCCESS"); //THIS HAPPENS anywhere from 4-7 times. I never get all 10 images. 

      dispatch_async(dispatch_get_main_queue(), ^{ 

       if (self.successBlock) 
        self.successBlock(finalAsset); 
      }); 


    }); 
} 

Изображения загружая хорошо, и когда я осмотреть мою базу данных я могу видеть BLOB данные для каждого «изображения BirdImage ». Проблема состоит в том, что из 10 изображений на самом деле их отображает случайное число (где-то от 4 до 7 из них при первом запуске). Затем, если я снова вернусь на этот экран, приложение закроется, без сообщений об ошибках или сбоев. Я подозреваю, что это своего рода блокировка Core Data.

Я знаю, что должен «получить доступ к контексту из того же потока, который его создал». Но если я получаю доступ к контексту в разных методах (например, в методах load и connectionDidFinishLoading выше), как я могу использовать один и тот же поток? Другими словами, как я могу изменить свой код, чтобы я выполнял потокобезопасное сохранение контекста CoreData изображений, когда они заканчивают загрузку?

+1

При загрузке этих изображений вы используете один и тот же объект для каждой загрузки или один объект для загрузки? –

+0

[Помогает?] (Http://stackoverflow.com/questions/11183456/core-data-multiple-threads-view-controller-not-updating). – HAS

+0

Основные данные не выбрасывают 'NSException'. Вы не должны обертывать вызовы сохранения в try/catch, как это, поскольку это может скрывать/маскировать ошибки в других частях вашего кода. –

ответ

0

NSManagedObjectContext имеет метод под названием performBlock: (или performBlockAndWait:), который, что неудивительно, принимает блок. Затем этот блок будет выполнен в потоке контекста. Вы можете использовать его в своих интересах, поставив код из connectionDidFinishLoading: в блоке, который вы передаете performBlock::

void (^contextBlock)() = ... // your code here 

[self.objectContext performBlock:contextBlock]; 

Если вам нужно сделать UI обновления или выполнения кода в другом потоке, вы можете использовать dispatch_async в том, что блок также.

+0

Я попытался поместить весь код в свой метод connectionDidFinishLoading в [self.objectContext performBlock:^{} ...], но получаю те же неустойчивые результаты. – soleil

+0

Обратитесь к операторам журнала, которые я добавил в connectionDidFinishLoading. Это показывает, где возникает проблема. – soleil

+0

Если я прокомментирую [[CoreDataController sharedController] saveContext]; Изображения будут отображаться. Но тогда они не сохраняются в Core Data. Итак, где я помещаю [[CoreDataController sharedController] saveContext] ;? – soleil

1

Прежде всего, я не уверен, что ваши изображения будут загружены на отдельном потоке

dispatch_async(dispatch_get_main_queue(), ^{ 
     self.assetURLRequest = [NSURLRequest requestWithURL:self.assetRequest.assetURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:self.assetRequest.timeOut]; 
     self.assetConnection = [[NSURLConnection alloc] initWithRequest:self.assetURLRequest delegate:self startImmediately:NO]; 
     [self.assetConnection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 
     [self.assetConnection start]; 

    }); 

dispatch_get_main_queue() возвращает основную очередь, связанная с основным потоком, чтобы ваш [NSRunLoop currentRunLoop] возвращает цикл выполнения основного потока, который не претати хорошо.

Во-вторых, не рекомендуется сохранять изображения в базе данных как blob, потому что размер базы данных резко возрастет, и ваши запросы и другая операция потребуют больше времени для выполнения, поэтому вы должны сохранить их локально (dir документов) или кеш их в течение определенного периода времени и сохранять в базе данных только путь к изображению.

В-третьих, self.imageEntity = birdImage это небезопасно, возможно, эта строка будет вызываться несколько раз, и загружается только одно изображение, поэтому вы теряете ссылку на свою сущность, и я думаю, что это основная причина, по которой ваши изображения не полностью скачал.

В-четвертых, вы должны положиться на использование AFNetworking и AFImageRequestOperation, это позаботится об асинхронной загрузке, и вы сможете сохранить изображения, сравнив URL-адрес изображения с URL-адресом объекта.

+0

Я понимаю, что не сохраняю изображения в базе данных. Но для этого приложения это необходимо. Я рассмотрю ваш третий пункт. – soleil

+0

@soleil вы также можете использовать четвертый и сохранить оттуда изображения в bg;) – danypata

+0

Планирование соединения на основном проходе здесь не проблема, так как OP явно планирует с помощью команды «NSRunLoopCommonModes», что фактически означает, что делегаты соединения не будут блокироваться событиями пользовательского интерфейса (например, прокруткой). Кроме того, OP довольно быстро отправляет из основного потока в другую очередь, не загрязняя основной поток. AFNetworking тоже не помогло бы, так как у соединения, похоже, нет проблем. Я подозреваю, что ошибка в том, как вызываются основные данные. @Scott имеет ответ. – CouchDeveloper

Смежные вопросы