简体   繁体   中英

CoreData Concurrency issue

I am having issue while using private managedObjectContext for saving data in background. I am new to CoreData. I am using Parent-Child approach for NSManagedObjectContext but facing several issues.

Errors arise when I tap reload button multiple times

Errors:

  1. 'NSGenericException', reason: Collection <__NSCFSet: 0x16e47100> was mutated while being enumerated

  2. Some times : crash here try managedObjectContext.save()

  3. Sometimes Key value coding Compliant error

My ViewController class

        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)")
                }
            }
        }
    }

You can write in background and read in the main thread (using different MOCs like you do). And actually you're almost doing it right.

The app crashes on the try managedObjectContext.save() line, because saveMainContext is called from within the private MOC's performBlock. The easiest way to fix it is to wrap the save operation into another performBlock :

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

Other two errors are a little more tricky. Please, provide more info. What object is not key-value compliant for what key? It's most likely a JSON parsing issue.

The first error ("mutated while being enumerated") is actually a nasty one. The description is pretty straight forward: a collection was mutated by one thread while it was enumerated on the other. Where does it occur? One possible reason (most likely one, I would say) is that it is indeed a Core Data multithreading issue. Despite the fact that you can use several threads, you can only use core data objects within the thread they were obtained on. If you pass them to another thread, you'll likely run into an error like this.

Look through your code and try to find a place where such situation might occur (for instance, do you access self.values from other classes?). Unfortunately, I wasn't able to find such place in several minutes. If you said upon which collection enumeration this error occurs, it would help).

UPDATE: PS I just thought that the error might be related to the saveMainContext function. It is performed right before a call to saved . saveMainContext is performed on the background thread (in the original code, I mean), and saved is performed on the main thread. So after fixing saveMainContext , the error might go away (I'm not 100% sure, though).

You are violating thread confinement.

You cannot write to CoreData in Background, and read in MainThread.

All operation on CoreData must be done in the same thread

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM