Search code examples
iosswiftxcodecore-databackground-thread

coredata update json background thread problem


I have the following coredata singleton class in my appdelegate i try to update the data from json but in my app when it launches i get different error messages regarding thread like

Main Thread Checker: UI API called or observer of NSManagedObjectContextObjectsDidChangeNotification or Incorrect guard value

What is the problem and what changes should i make for it to work? thanks

import CoreData
import Foundation
class CoreDataStack {
   
  static let sharedManager = CoreDataStack()
  private init() {} // Prevent clients from creating another instance.
   
  //This is the name of your coreData Database
  static let modelName = "myDB"
  static let FirstLaunchKey = "firstLaunch"
 
  lazy var managedObjectModel: NSManagedObjectModel = {
    let modelURL = Bundle.main.url(forResource: CoreDataStack.modelName, withExtension: "momd")!
    return NSManagedObjectModel(contentsOf: modelURL)!
  }()
   
   
  lazy var applicationDocumentsDirectory: URL = {
    return NSPersistentContainer.defaultDirectoryURL()
  }()
   
  lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
    let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
     
    let persistentStoreURL = self.applicationDocumentsDirectory.appendingPathComponent(CoreDataStack.modelName + ".sqlite")
     
    do {
       
      // let dict = [NSSQLitePragmasOption: ["journal_mode":"DELETE"]]
  try coordinator.addPersistentStore(ofType: NSSQLiteStoreType,
                    configurationName: nil,
                    at: persistentStoreURL,
              options: [NSMigratePersistentStoresAutomaticallyOption: true,
                   NSInferMappingModelAutomaticallyOption: false])
    } catch {
      fatalError("Persistent store error! \(error)")
    }
     
    return coordinator
  }()
   
   
  fileprivate lazy var saveManagedObjectContext: NSManagedObjectContext = {
    let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
    moc.persistentStoreCoordinator = self.persistentStoreCoordinator
    return moc
  }()
   
  @objc lazy var mainObjectContext: NSManagedObjectContext = {
    let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
    managedObjectContext.parent = self.saveManagedObjectContext
    return managedObjectContext
  }()
   
  func saveMainContext() {
    guard mainObjectContext.hasChanges || saveManagedObjectContext.hasChanges else {
      return
    }
 
    mainObjectContext.performAndWait() {
      // print("save performAndWait")
      do {
        try self.mainObjectContext.save()
      } catch {
        fatalError("Error saving main managed object context! \(error)")
      }
    }
    saveManagedObjectContext.perform() {
      // print("save simple")
      do {
        try self.saveManagedObjectContext.save()
      } catch {
        fatalError("Error saving private managed object context! \(error)")
      }
    }
  }
  func removeDatabaseForVersion(version:String){
    let previouslyVersion = UserDefaults.standard.bool(forKey: version)
     if !previouslyVersion {
      UserDefaults.standard.set(true, forKey: version)
      // Default directory where the CoreDataStack will store its files
      let directory = NSPersistentContainer.defaultDirectoryURL()
      let url = directory.appendingPathComponent(CoreDataStack.modelName + ".sqlite")
       
      let shmURL = directory.appendingPathComponent(CoreDataStack.modelName + ".sqlite-shm")
       
      let walURL = directory.appendingPathComponent(CoreDataStack.modelName + ".sqlite-wal")
       
      _ = try? FileManager.default.removeItem(at: url)
      _ = try? FileManager.default.removeItem(at: shmURL)
      _ = try? FileManager.default.removeItem(at: walURL)
    }
  }
   
}

in my appdelegate:

UpdateDbClass.updateDatabase(entityName: DbTable.VehiclesEntity.rawValue, completionHandler: {
      print(" DB updated delegate")
    })

in updatedb class:

import UIKit
import Alamofire
import CoreData


enum LoaderError:String{
    case
    JsonFailed,
    PathFailed,
    NoEntityDescription,
    UnknownError
}

enum DbTable:String{
    case
    VehiclesEntity,
    PhotosEntity,
    ModelsEntity,
    NewsEntity,
    StylesEntity
}

class UpdateDbClass {
    
    
    static func updateDatabase(entityName:String,completionHandler: @escaping () -> Void){
      var url = URL(string: UrlRepository.VehiclesJsonUrl!)!
      
   
      var table = ""
          switch entityName {
          case DbTable.VehiclesEntity.rawValue:
              table = "Vehicles"
              url = URL(string: UrlRepository.VehiclesJsonUrl!)!
          case DbTable.PhotosEntity.rawValue:
              table = "Photos"
              url = URL(string: UrlRepository.PhotosJsonUrl!)!
               table = "Styles"
                url = Bundle.main.url(forResource: "Styles", withExtension: "json")!
                  // url = URL(string: UrlRepository.NewsJsonUrl!)!
          default:
              break
          }
          let uuid = UUID().uuidString
          let parameters: Parameters = [
              "id": uuid
          ]

     let queue = DispatchQueue(label: "com.my.test", qos: .background, attributes: .concurrent)
    

      AF.request(url, method: .get, parameters: parameters, encoding: URLEncoding(destination: .queryString), headers: nil).responseJSON(queue:queue){ response in
              switch response.result {
              case let .success(value):
                  if let items = value as? [[String: Any]] {

                      var itemsArray:[Int32] = []
                      for item in items{
                          if let id = item["id"] as? Int32{
                               itemsArray.append(id)
                          }
                      }

                      guard let entity = NSEntityDescription.entity(forEntityName: table, in:(CoreDataStack.sharedManager.mainObjectContext)) else {
                          fatalError("Could not find entity descriptions!")
                      }

              switch entityName {

                  case DbTable.StylesEntity.rawValue: //Styles
                      checkDeletedRecords(jsonItems: itemsArray,table: table)

                      for item in items{
                          guard let id = item["id"] as? Int32 else {return}
                          //Check if not exists
                          if  !CheckIfExists(id: id,table:table){
                             print("id \(id) does not exist")
                              //Insert Record
                              let object = Styles(entity: entity, insertInto: CoreDataStack.sharedManager.mainObjectContext)
                      object.setValue(item["id"], forKey: "id")
                      object.setValue(item["style"] as! String, forKey: "style")
                      object.setValue(item["image"] as! String, forKey: "image")
         
                      CoreDataStack.sharedManager.saveMainContext()
                          }
                          else{    //Update Record
                             // print("id \(item["id"]) exists")

                              do{
                                  let fetchRequest = NSFetchRequest<Styles>(entityName:"Styles")
                                  let predicate = NSPredicate(format: "id == %d",item["id"] as! Int32)
                                  fetchRequest.predicate = predicate
                                  let req =  try CoreDataStack.sharedManager.mainObjectContext.fetch(fetchRequest)
                                  let object = req[0] as NSManagedObject
                                object.setValue(item["style"] as! String, forKey: "style")
                                 object.setValue(item["image"] as! String, forKey: "image")
                                

                                   CoreDataStack.sharedManager.saveMainContext()

                              }catch{
                                  print("there was an error")
                              }
                              completionHandler()
                          }
                      }
                      break;

             
                      default:
                          break
                      }
                   }
                  break
              case let .failure(error):
                  print(error as NSError)
                  break
              }
          }
      

       }
      


  
}

protocol CoreDataWorkerProtocol {
    associatedtype EntityType
}

enum VoidResult {
    case success
    case failure(NSError)
}


Solution

  • Response json is called on background thread and you are trying to use viewContext in background thread.

    You should use perform block if you want to use viewContext in background.

    For example like this

      AF.request(:).responseJSON(queue:queue){ response in
           let mainContext = CoreDataStack.sharedManager.mainObjectContext
            mainContext.perform { 
            guard let entity = NSEntityDescription.entity(forEntityName: table, in: mainContext) else {
                    fatalError("Could not find entity descriptions!")
                }    
            }
       }