2016-06-02 4 views
0

У меня проблема при использовании private managedObjectContext для сохранения данных в фоновом режиме. Я новичок в CoreData. Я использую подход «Родитель-ребенок» для NSManagedObjectContext, но перед несколькими проблемами.Проблема параллелизма CoreData

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

Ошибки:

  1. 'NSGenericException', причина: Коллекция < __NSCFSet: 0x16e47100> мутировали в то же время перечисленные

  2. Несколько раз: авария здесь try managedObjectContext.save()

  3. Иногда значение ключа кодирования Соответствует ошибка

Мой ViewController класс

 class ViewController: UIViewController { 
      var jsonObj:NSDictionary? 
      var values = [AnyObject]() 
      @IBOutlet weak var tableView:UITableView! 

      override func viewDidLoad() { 
       super.viewDidLoad() 
       getData() 
       saveInBD() 
       NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.saved(_:)), name: "kContextSavedNotification", object: nil) 
      } 
    //Loding json data from a json file 

      func getData(){ 
      if let path = NSBundle.mainBundle().pathForResource("countries", ofType: "json") { 
      do { 
      let data = try NSData(contentsOfURL: NSURL(fileURLWithPath: path), options: NSDataReadingOptions.DataReadingMappedIfSafe) 


      do { 
      jsonObj = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers) as? NSDictionary 

      } catch { 
      jsonObj = nil; 
      } 


      } catch let error as NSError { 
      print(error.localizedDescription) 
      } 
      } else { 
      print("Invalid filename/path.") 
      } 
      } 
      **Notification reciever** 

      func saved(not:NSNotification){ 
       dispatch_async(dispatch_get_main_queue()) { 
        if let data = DatabaseManager.sharedInstance.getAllNews(){ 
         self.values = data 
         print(data.count) 
         self.tableView.reloadData() 

        } 

       } 
       } 

      func saveInBD(){ 
       if jsonObj != nil { 
        guard let nameArray = jsonObj?["data#"] as? NSArray else{return} 
        DatabaseManager.sharedInstance.addNewsInBackGround(nameArray) 
       } 
      } 
      //UIButton for re-saving data again 

      @IBAction func reloadAxn(sender: UIButton) { 
       saveInBD() 
      } 

    } 


    **Database Manager Class** 

    public class DatabaseManager{ 

     static let sharedInstance = DatabaseManager() 

     let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext 

     private init() { 
     } 

     func addNewsInBackGround(arr:NSArray) { 
      let jsonArray = arr 
      let moc = managedObjectContext 

      let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType) 
      privateMOC.parentContext = moc 

       privateMOC.performBlock { 
        for jsonObject in jsonArray { 
         let entity = NSEntityDescription.entityForName("Country", 
          inManagedObjectContext:privateMOC) 

         let managedObject = NSManagedObject(entity: entity!, 
          insertIntoManagedObjectContext: privateMOC) as! Country 

         managedObject.name = jsonObject.objectForKey("name")as? String 

        } 


        do { 
         try privateMOC.save() 

         self.saveMainContext() 

         NSNotificationCenter.defaultCenter().postNotificationName("kContextSavedNotification", object: nil) 
        } catch { 
         fatalError("Failure to save context: \(error)") 
        } 
       } 

     } 





     func getAllNews()->([AnyObject]?){ 
      let fetchRequest = NSFetchRequest(entityName: "Country") 
      fetchRequest.resultType = NSFetchRequestResultType.DictionaryResultType 

      do { 
       let results = 
        try managedObjectContext.executeFetchRequest(fetchRequest) 
       results as? [NSDictionary] 
       if results.count > 0 
       { 
        return results 
       }else 
       { 
        return nil 
       } 
      } catch let error as NSError { 
       print("Could not fetch \(error), \(error.userInfo)") 
       return nil 
      } 
     } 

     func saveMainContext() { 
      if managedObjectContext.hasChanges { 
       do { 
        try managedObjectContext.save() 
       } catch { 
        let nserror = error as NSError 
        print("Unresolved error \(nserror), \(nserror.userInfo)") 
       } 
      } 
     } 
    } 
+0

http://stackoverflow.com/questions/10157381/avoiding-nsarray-was-mutated-while-being-enumerated –

ответ

3

You может записи в фоновом режиме и читать в главном потоке (с использованием различных МОС, как вы делаете). И на самом деле вы почти все делаете правильно.

Приложение аварийно завершает работу по линии try managedObjectContext.save(), потому что saveMainContext вызывается из внутреннего исполнения MOC. Самый простой способ исправить это, чтобы обернуть операцию сохранения в другой performBlock:

func saveMainContext() { 
    managedObjectContext.performBlock { 
     if managedObjectContext.hasChanges { 
      do { 
       try managedObjectContext.save() 
      } catch { 
       let nserror = error as NSError 
       print("Unresolved error \(nserror), \(nserror.userInfo)") 
      } 
     } 
    } 
} 

Другие две ошибки немного сложнее. Пожалуйста, предоставьте дополнительную информацию. Какой объект не соответствует ключевому значению для какого ключа? Это, скорее всего, проблема синтаксического анализа JSON.

Первая ошибка («мутированная при перечислении») на самом деле является отвратительной. Описание довольно прямолинейно: коллекция была мутирована одним потоком, пока она была переписана с другой. Где это происходит? Одна из возможных причин (скорее всего, одна из них, я бы сказал) заключается в том, что это действительно проблема многопоточности Core Data. Несмотря на то, что вы можете использовать несколько потоков, вы можете использовать только основные объекты данных в потоке, на котором они были получены. Если вы передадите их в другой поток, вы, вероятно, столкнетесь с такой ошибкой.

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

ОБНОВЛЕНИЕ: P.S. Я просто подумал, что ошибка может быть связана с функцией saveMainContext.Он выполняется непосредственно перед вызовом saved. saveMainContext выполняется в фоновом потоке (в исходном коде, я имею в виду), а saved выполняется в основном потоке. Поэтому после исправления saveMainContext ошибка может исчезнуть (я не уверен на 100%, хотя).

+0

следует ли мне вызвать saveMainContext() из частного блока и использовать его, поскольку вы предложили 'managedObjectContext.performBlock {}' только там, где это определено. – ankit

+0

@ NA000022, да, он должен работать. Другой способ - оставить сам метод saveMainContext неизменным, но заверните его в 'dispatch_async (dispatch_get_main_queue())'. Первый случай лучше, на мой взгляд, потому что во втором случае нам нужно было бы запомнить эту функцию, когда мы ее называем. Наша цель - обеспечить, чтобы мы только вызывали 'managedObjectContext.save()' в основном потоке. То, как мы это делаем, не так важно. – FreeNickname

+0

Он работает отлично, но теперь я сталкиваюсь с другой проблемой. Если я удаляю reloadAction(), который используется для сохранения данных в приватном MOC, пользовательский интерфейс зависает в течение 1 секунды и непрерывно увеличивается по мере роста записей. – ankit

-1

Вы нарушаете удержания нити.

Вы не можете писать в CoreData в фоновом режиме и читать в MainThread.

Все операции на CoreData должны быть сделаны в том же потоке

+0

Не следует использовать основной поток, пока я получаю уведомление. – ankit

+0

Использование MainThread Или backgroundThread для обеих операций. – CZ54

+0

Как я могу использовать тот же поток, что и приватная очередь, на другом потоке – ankit