Problems saving NSManagedObjects on a background Context












0















I've been struggling with this for days. I'll appreciate any help.



I have a Location NSManagedObject and an Image NSManagedObject, they have one-to-many relationship, i.e., one location has many images.



I have 2 screens, in the first one the user adds locations on the view context and they get added and retrieved without problems.



Now, in the second screen, I want to retrieve images based on the location selected in the first screen, then display the images in a Collection View. The images are first retrieved from flickr, then saved in the DB.



I want to save and retrieve images on a background context and this causes me a lot of problems.




  1. When I try to save every image retrieved from flickr I get a warning stating that there is a dangling object and the relationship can' be established:


This is my saving code:



  func saveImagesToDb () {

//Store the image in the DB along with its location on the background thread
if (doesImageExist()){
dataController.backgroundContext.perform {

for downloadedImage in self.downloadedImages {
print ("saving to context")
let imageOnMainContext = Image (context: self.dataController.viewContext)
let imageManagedObjectId = imageOnMainContext.objectID
let imageOnBackgroundContext = self.dataController.backgroundContext.object(with: imageManagedObjectId) as! Image

let locationObjectId = self.imagesLocation.objectID
let locationOnBackgroundContext = self.dataController.backgroundContext.object(with: locationObjectId) as! Location

let imageData = NSData (data: downloadedImage.jpegData(compressionQuality: 0.5)!)
imageOnBackgroundContext.image = imageData as Data
imageOnBackgroundContext.location = locationOnBackgroundContext


try? self.dataController.backgroundContext.save ()
}
}
}
}


As you can see in the code above I'm building NSManagedObject on the background context based on the ID retrieved from those on the view context. Every time saveImagesToDb is called I get the warning, so what's the problem?




  1. In spite of the warning above, when I retrieve the data through a FetchedResultsController (which works on the background context). The Collection View sometimes view the images just fine and sometimes I get this error:


Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (4) must be equal to the number of items contained in that section before the update (1), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'



Here are some code snippets that are related to setting up the FetchedResultsController and updating the Collection View based on changes in the context or in the FetchedResultsController.



  func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

guard let imagesCount = fetchedResultsController.fetchedObjects?.count else {return 0}

return imagesCount
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
print ("cell data")
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as! ImageCell
//cell.placeImage.image = UIImage (named: "placeholder")

let imageObject = fetchedResultsController.object(at: indexPath)
let imageData = imageObject.image
let uiImage = UIImage (data: imageData!)

cell.placeImage.image = uiImage
return cell
}



func setUpFetchedResultsController () {
print ("setting up controller")
//Build a request for the Image ManagedObject
let fetchRequest : NSFetchRequest <Image> = Image.fetchRequest()
//Fetch the images only related to the images location

let locationObjectId = self.imagesLocation.objectID
let locationOnBackgroundContext = self.dataController.backgroundContext.object(with: locationObjectId) as! Location
let predicate = NSPredicate (format: "location == %@", locationOnBackgroundContext)

fetchRequest.predicate = predicate
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "location", ascending: true)]

fetchedResultsController = NSFetchedResultsController (fetchRequest: fetchRequest, managedObjectContext: dataController.backgroundContext, sectionNameKeyPath: nil, cacheName: "(latLongString) images")

fetchedResultsController.delegate = self

do {
try fetchedResultsController.performFetch ()
} catch {
fatalError("couldn't retrive images for the selected location")
}
}

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {

print ("object info changed in fecthed controller")

switch type {
case .insert:
print ("insert")
DispatchQueue.main.async {
print ("calling section items")
self.collectionView!.numberOfItems(inSection: 0)
self.collectionView.insertItems(at: [newIndexPath!])
}
break

case .delete:
print ("delete")

DispatchQueue.main.async {
self.collectionView!.numberOfItems(inSection: 0)
self.collectionView.deleteItems(at: [indexPath!])
}
break
case .update:
print ("update")

DispatchQueue.main.async {
self.collectionView!.numberOfItems(inSection: 0)
self.collectionView.reloadItems(at: [indexPath!])
}
break
case .move:
print ("move")

DispatchQueue.main.async {
self.collectionView!.numberOfItems(inSection: 0)
self.collectionView.moveItem(at: indexPath!, to: newIndexPath!)

}

}
}

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
print ("section info changed in fecthed controller")
let indexSet = IndexSet(integer: sectionIndex)
switch type {
case .insert:
self.collectionView!.numberOfItems(inSection: 0)
collectionView.insertSections(indexSet)
break
case .delete:
self.collectionView!.numberOfItems(inSection: 0)
collectionView.deleteSections(indexSet)
case .update, .move:
fatalError("Invalid change type in controller(_:didChange:atSectionIndex:for:). Only .insert or .delete should be possible.")
}

}

func addSaveNotificationObserver() {
removeSaveNotificationObserver()
print ("context onbserver notified")
saveObserverToken = NotificationCenter.default.addObserver(forName: .NSManagedObjectContextObjectsDidChange, object: dataController?.backgroundContext, queue: nil, using: handleSaveNotification(notification:))
}

func removeSaveNotificationObserver() {
if let token = saveObserverToken {
NotificationCenter.default.removeObserver(token)
}
}

func handleSaveNotification(notification:Notification) {
DispatchQueue.main.async {
self.collectionView!.numberOfItems(inSection: 0)
self.collectionView.reloadData()
}
}


What am I doing wrong? I'll appreciate any help.










share|improve this question



























    0















    I've been struggling with this for days. I'll appreciate any help.



    I have a Location NSManagedObject and an Image NSManagedObject, they have one-to-many relationship, i.e., one location has many images.



    I have 2 screens, in the first one the user adds locations on the view context and they get added and retrieved without problems.



    Now, in the second screen, I want to retrieve images based on the location selected in the first screen, then display the images in a Collection View. The images are first retrieved from flickr, then saved in the DB.



    I want to save and retrieve images on a background context and this causes me a lot of problems.




    1. When I try to save every image retrieved from flickr I get a warning stating that there is a dangling object and the relationship can' be established:


    This is my saving code:



      func saveImagesToDb () {

    //Store the image in the DB along with its location on the background thread
    if (doesImageExist()){
    dataController.backgroundContext.perform {

    for downloadedImage in self.downloadedImages {
    print ("saving to context")
    let imageOnMainContext = Image (context: self.dataController.viewContext)
    let imageManagedObjectId = imageOnMainContext.objectID
    let imageOnBackgroundContext = self.dataController.backgroundContext.object(with: imageManagedObjectId) as! Image

    let locationObjectId = self.imagesLocation.objectID
    let locationOnBackgroundContext = self.dataController.backgroundContext.object(with: locationObjectId) as! Location

    let imageData = NSData (data: downloadedImage.jpegData(compressionQuality: 0.5)!)
    imageOnBackgroundContext.image = imageData as Data
    imageOnBackgroundContext.location = locationOnBackgroundContext


    try? self.dataController.backgroundContext.save ()
    }
    }
    }
    }


    As you can see in the code above I'm building NSManagedObject on the background context based on the ID retrieved from those on the view context. Every time saveImagesToDb is called I get the warning, so what's the problem?




    1. In spite of the warning above, when I retrieve the data through a FetchedResultsController (which works on the background context). The Collection View sometimes view the images just fine and sometimes I get this error:


    Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (4) must be equal to the number of items contained in that section before the update (1), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'



    Here are some code snippets that are related to setting up the FetchedResultsController and updating the Collection View based on changes in the context or in the FetchedResultsController.



      func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

    guard let imagesCount = fetchedResultsController.fetchedObjects?.count else {return 0}

    return imagesCount
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    print ("cell data")
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as! ImageCell
    //cell.placeImage.image = UIImage (named: "placeholder")

    let imageObject = fetchedResultsController.object(at: indexPath)
    let imageData = imageObject.image
    let uiImage = UIImage (data: imageData!)

    cell.placeImage.image = uiImage
    return cell
    }



    func setUpFetchedResultsController () {
    print ("setting up controller")
    //Build a request for the Image ManagedObject
    let fetchRequest : NSFetchRequest <Image> = Image.fetchRequest()
    //Fetch the images only related to the images location

    let locationObjectId = self.imagesLocation.objectID
    let locationOnBackgroundContext = self.dataController.backgroundContext.object(with: locationObjectId) as! Location
    let predicate = NSPredicate (format: "location == %@", locationOnBackgroundContext)

    fetchRequest.predicate = predicate
    fetchRequest.sortDescriptors = [NSSortDescriptor(key: "location", ascending: true)]

    fetchedResultsController = NSFetchedResultsController (fetchRequest: fetchRequest, managedObjectContext: dataController.backgroundContext, sectionNameKeyPath: nil, cacheName: "(latLongString) images")

    fetchedResultsController.delegate = self

    do {
    try fetchedResultsController.performFetch ()
    } catch {
    fatalError("couldn't retrive images for the selected location")
    }
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {

    print ("object info changed in fecthed controller")

    switch type {
    case .insert:
    print ("insert")
    DispatchQueue.main.async {
    print ("calling section items")
    self.collectionView!.numberOfItems(inSection: 0)
    self.collectionView.insertItems(at: [newIndexPath!])
    }
    break

    case .delete:
    print ("delete")

    DispatchQueue.main.async {
    self.collectionView!.numberOfItems(inSection: 0)
    self.collectionView.deleteItems(at: [indexPath!])
    }
    break
    case .update:
    print ("update")

    DispatchQueue.main.async {
    self.collectionView!.numberOfItems(inSection: 0)
    self.collectionView.reloadItems(at: [indexPath!])
    }
    break
    case .move:
    print ("move")

    DispatchQueue.main.async {
    self.collectionView!.numberOfItems(inSection: 0)
    self.collectionView.moveItem(at: indexPath!, to: newIndexPath!)

    }

    }
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
    print ("section info changed in fecthed controller")
    let indexSet = IndexSet(integer: sectionIndex)
    switch type {
    case .insert:
    self.collectionView!.numberOfItems(inSection: 0)
    collectionView.insertSections(indexSet)
    break
    case .delete:
    self.collectionView!.numberOfItems(inSection: 0)
    collectionView.deleteSections(indexSet)
    case .update, .move:
    fatalError("Invalid change type in controller(_:didChange:atSectionIndex:for:). Only .insert or .delete should be possible.")
    }

    }

    func addSaveNotificationObserver() {
    removeSaveNotificationObserver()
    print ("context onbserver notified")
    saveObserverToken = NotificationCenter.default.addObserver(forName: .NSManagedObjectContextObjectsDidChange, object: dataController?.backgroundContext, queue: nil, using: handleSaveNotification(notification:))
    }

    func removeSaveNotificationObserver() {
    if let token = saveObserverToken {
    NotificationCenter.default.removeObserver(token)
    }
    }

    func handleSaveNotification(notification:Notification) {
    DispatchQueue.main.async {
    self.collectionView!.numberOfItems(inSection: 0)
    self.collectionView.reloadData()
    }
    }


    What am I doing wrong? I'll appreciate any help.










    share|improve this question

























      0












      0








      0








      I've been struggling with this for days. I'll appreciate any help.



      I have a Location NSManagedObject and an Image NSManagedObject, they have one-to-many relationship, i.e., one location has many images.



      I have 2 screens, in the first one the user adds locations on the view context and they get added and retrieved without problems.



      Now, in the second screen, I want to retrieve images based on the location selected in the first screen, then display the images in a Collection View. The images are first retrieved from flickr, then saved in the DB.



      I want to save and retrieve images on a background context and this causes me a lot of problems.




      1. When I try to save every image retrieved from flickr I get a warning stating that there is a dangling object and the relationship can' be established:


      This is my saving code:



        func saveImagesToDb () {

      //Store the image in the DB along with its location on the background thread
      if (doesImageExist()){
      dataController.backgroundContext.perform {

      for downloadedImage in self.downloadedImages {
      print ("saving to context")
      let imageOnMainContext = Image (context: self.dataController.viewContext)
      let imageManagedObjectId = imageOnMainContext.objectID
      let imageOnBackgroundContext = self.dataController.backgroundContext.object(with: imageManagedObjectId) as! Image

      let locationObjectId = self.imagesLocation.objectID
      let locationOnBackgroundContext = self.dataController.backgroundContext.object(with: locationObjectId) as! Location

      let imageData = NSData (data: downloadedImage.jpegData(compressionQuality: 0.5)!)
      imageOnBackgroundContext.image = imageData as Data
      imageOnBackgroundContext.location = locationOnBackgroundContext


      try? self.dataController.backgroundContext.save ()
      }
      }
      }
      }


      As you can see in the code above I'm building NSManagedObject on the background context based on the ID retrieved from those on the view context. Every time saveImagesToDb is called I get the warning, so what's the problem?




      1. In spite of the warning above, when I retrieve the data through a FetchedResultsController (which works on the background context). The Collection View sometimes view the images just fine and sometimes I get this error:


      Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (4) must be equal to the number of items contained in that section before the update (1), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'



      Here are some code snippets that are related to setting up the FetchedResultsController and updating the Collection View based on changes in the context or in the FetchedResultsController.



        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

      guard let imagesCount = fetchedResultsController.fetchedObjects?.count else {return 0}

      return imagesCount
      }

      func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
      print ("cell data")
      let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as! ImageCell
      //cell.placeImage.image = UIImage (named: "placeholder")

      let imageObject = fetchedResultsController.object(at: indexPath)
      let imageData = imageObject.image
      let uiImage = UIImage (data: imageData!)

      cell.placeImage.image = uiImage
      return cell
      }



      func setUpFetchedResultsController () {
      print ("setting up controller")
      //Build a request for the Image ManagedObject
      let fetchRequest : NSFetchRequest <Image> = Image.fetchRequest()
      //Fetch the images only related to the images location

      let locationObjectId = self.imagesLocation.objectID
      let locationOnBackgroundContext = self.dataController.backgroundContext.object(with: locationObjectId) as! Location
      let predicate = NSPredicate (format: "location == %@", locationOnBackgroundContext)

      fetchRequest.predicate = predicate
      fetchRequest.sortDescriptors = [NSSortDescriptor(key: "location", ascending: true)]

      fetchedResultsController = NSFetchedResultsController (fetchRequest: fetchRequest, managedObjectContext: dataController.backgroundContext, sectionNameKeyPath: nil, cacheName: "(latLongString) images")

      fetchedResultsController.delegate = self

      do {
      try fetchedResultsController.performFetch ()
      } catch {
      fatalError("couldn't retrive images for the selected location")
      }
      }

      func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {

      print ("object info changed in fecthed controller")

      switch type {
      case .insert:
      print ("insert")
      DispatchQueue.main.async {
      print ("calling section items")
      self.collectionView!.numberOfItems(inSection: 0)
      self.collectionView.insertItems(at: [newIndexPath!])
      }
      break

      case .delete:
      print ("delete")

      DispatchQueue.main.async {
      self.collectionView!.numberOfItems(inSection: 0)
      self.collectionView.deleteItems(at: [indexPath!])
      }
      break
      case .update:
      print ("update")

      DispatchQueue.main.async {
      self.collectionView!.numberOfItems(inSection: 0)
      self.collectionView.reloadItems(at: [indexPath!])
      }
      break
      case .move:
      print ("move")

      DispatchQueue.main.async {
      self.collectionView!.numberOfItems(inSection: 0)
      self.collectionView.moveItem(at: indexPath!, to: newIndexPath!)

      }

      }
      }

      func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
      print ("section info changed in fecthed controller")
      let indexSet = IndexSet(integer: sectionIndex)
      switch type {
      case .insert:
      self.collectionView!.numberOfItems(inSection: 0)
      collectionView.insertSections(indexSet)
      break
      case .delete:
      self.collectionView!.numberOfItems(inSection: 0)
      collectionView.deleteSections(indexSet)
      case .update, .move:
      fatalError("Invalid change type in controller(_:didChange:atSectionIndex:for:). Only .insert or .delete should be possible.")
      }

      }

      func addSaveNotificationObserver() {
      removeSaveNotificationObserver()
      print ("context onbserver notified")
      saveObserverToken = NotificationCenter.default.addObserver(forName: .NSManagedObjectContextObjectsDidChange, object: dataController?.backgroundContext, queue: nil, using: handleSaveNotification(notification:))
      }

      func removeSaveNotificationObserver() {
      if let token = saveObserverToken {
      NotificationCenter.default.removeObserver(token)
      }
      }

      func handleSaveNotification(notification:Notification) {
      DispatchQueue.main.async {
      self.collectionView!.numberOfItems(inSection: 0)
      self.collectionView.reloadData()
      }
      }


      What am I doing wrong? I'll appreciate any help.










      share|improve this question














      I've been struggling with this for days. I'll appreciate any help.



      I have a Location NSManagedObject and an Image NSManagedObject, they have one-to-many relationship, i.e., one location has many images.



      I have 2 screens, in the first one the user adds locations on the view context and they get added and retrieved without problems.



      Now, in the second screen, I want to retrieve images based on the location selected in the first screen, then display the images in a Collection View. The images are first retrieved from flickr, then saved in the DB.



      I want to save and retrieve images on a background context and this causes me a lot of problems.




      1. When I try to save every image retrieved from flickr I get a warning stating that there is a dangling object and the relationship can' be established:


      This is my saving code:



        func saveImagesToDb () {

      //Store the image in the DB along with its location on the background thread
      if (doesImageExist()){
      dataController.backgroundContext.perform {

      for downloadedImage in self.downloadedImages {
      print ("saving to context")
      let imageOnMainContext = Image (context: self.dataController.viewContext)
      let imageManagedObjectId = imageOnMainContext.objectID
      let imageOnBackgroundContext = self.dataController.backgroundContext.object(with: imageManagedObjectId) as! Image

      let locationObjectId = self.imagesLocation.objectID
      let locationOnBackgroundContext = self.dataController.backgroundContext.object(with: locationObjectId) as! Location

      let imageData = NSData (data: downloadedImage.jpegData(compressionQuality: 0.5)!)
      imageOnBackgroundContext.image = imageData as Data
      imageOnBackgroundContext.location = locationOnBackgroundContext


      try? self.dataController.backgroundContext.save ()
      }
      }
      }
      }


      As you can see in the code above I'm building NSManagedObject on the background context based on the ID retrieved from those on the view context. Every time saveImagesToDb is called I get the warning, so what's the problem?




      1. In spite of the warning above, when I retrieve the data through a FetchedResultsController (which works on the background context). The Collection View sometimes view the images just fine and sometimes I get this error:


      Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (4) must be equal to the number of items contained in that section before the update (1), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'



      Here are some code snippets that are related to setting up the FetchedResultsController and updating the Collection View based on changes in the context or in the FetchedResultsController.



        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

      guard let imagesCount = fetchedResultsController.fetchedObjects?.count else {return 0}

      return imagesCount
      }

      func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
      print ("cell data")
      let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "photoCell", for: indexPath) as! ImageCell
      //cell.placeImage.image = UIImage (named: "placeholder")

      let imageObject = fetchedResultsController.object(at: indexPath)
      let imageData = imageObject.image
      let uiImage = UIImage (data: imageData!)

      cell.placeImage.image = uiImage
      return cell
      }



      func setUpFetchedResultsController () {
      print ("setting up controller")
      //Build a request for the Image ManagedObject
      let fetchRequest : NSFetchRequest <Image> = Image.fetchRequest()
      //Fetch the images only related to the images location

      let locationObjectId = self.imagesLocation.objectID
      let locationOnBackgroundContext = self.dataController.backgroundContext.object(with: locationObjectId) as! Location
      let predicate = NSPredicate (format: "location == %@", locationOnBackgroundContext)

      fetchRequest.predicate = predicate
      fetchRequest.sortDescriptors = [NSSortDescriptor(key: "location", ascending: true)]

      fetchedResultsController = NSFetchedResultsController (fetchRequest: fetchRequest, managedObjectContext: dataController.backgroundContext, sectionNameKeyPath: nil, cacheName: "(latLongString) images")

      fetchedResultsController.delegate = self

      do {
      try fetchedResultsController.performFetch ()
      } catch {
      fatalError("couldn't retrive images for the selected location")
      }
      }

      func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {

      print ("object info changed in fecthed controller")

      switch type {
      case .insert:
      print ("insert")
      DispatchQueue.main.async {
      print ("calling section items")
      self.collectionView!.numberOfItems(inSection: 0)
      self.collectionView.insertItems(at: [newIndexPath!])
      }
      break

      case .delete:
      print ("delete")

      DispatchQueue.main.async {
      self.collectionView!.numberOfItems(inSection: 0)
      self.collectionView.deleteItems(at: [indexPath!])
      }
      break
      case .update:
      print ("update")

      DispatchQueue.main.async {
      self.collectionView!.numberOfItems(inSection: 0)
      self.collectionView.reloadItems(at: [indexPath!])
      }
      break
      case .move:
      print ("move")

      DispatchQueue.main.async {
      self.collectionView!.numberOfItems(inSection: 0)
      self.collectionView.moveItem(at: indexPath!, to: newIndexPath!)

      }

      }
      }

      func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
      print ("section info changed in fecthed controller")
      let indexSet = IndexSet(integer: sectionIndex)
      switch type {
      case .insert:
      self.collectionView!.numberOfItems(inSection: 0)
      collectionView.insertSections(indexSet)
      break
      case .delete:
      self.collectionView!.numberOfItems(inSection: 0)
      collectionView.deleteSections(indexSet)
      case .update, .move:
      fatalError("Invalid change type in controller(_:didChange:atSectionIndex:for:). Only .insert or .delete should be possible.")
      }

      }

      func addSaveNotificationObserver() {
      removeSaveNotificationObserver()
      print ("context onbserver notified")
      saveObserverToken = NotificationCenter.default.addObserver(forName: .NSManagedObjectContextObjectsDidChange, object: dataController?.backgroundContext, queue: nil, using: handleSaveNotification(notification:))
      }

      func removeSaveNotificationObserver() {
      if let token = saveObserverToken {
      NotificationCenter.default.removeObserver(token)
      }
      }

      func handleSaveNotification(notification:Notification) {
      DispatchQueue.main.async {
      self.collectionView!.numberOfItems(inSection: 0)
      self.collectionView.reloadData()
      }
      }


      What am I doing wrong? I'll appreciate any help.







      ios swift multithreading core-data data-persistence






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Jan 2 at 10:21









      DaniaDania

      80521538




      80521538
























          4 Answers
          4






          active

          oldest

          votes


















          1














          I cannot tell you what the problem is with 1), but I think 2) is not (just) a problem with the database.



          The error your are getting usually happens when you add or remove items/sections to a collectionview, but when numberOfItemsInSection is called afterwards the numbers don't add up. Example: you have 5 items and add 2, but then numberOfItemsInSection is called and returns 6, which creates the inconsistency.



          In your case my guess would be that you add items with collectionView.insertItems(), but this line returns 0 afterwards:



          guard let imagesCount = fetchedResultsController.fetchedObjects?.count else {return 0}


          What also confused me in your code are these parts:



           DispatchQueue.main.async {
          print ("calling section items")
          self.collectionView!.numberOfItems(inSection: 0)
          self.collectionView.insertItems(at: [newIndexPath!])
          }


          You are requesting the number of items there, but you don't actually do anything with the result of the function. Is there a reason for that?



          Even though I don't know what the CoreData issue is I would advise you to not access the DB in the tableview delegate methods, but to have an array of items that is fetched once and is updated only when the db content changes. That is probably more performant and a lot easier to maintain.






          share|improve this answer
























          • Thanks @Robin Bork. As for the line that looks confusing, I used it based on Michael Su answer here: stackoverflow.com/questions/19199985/…. The discussion states that the problem is from a bug in Collection View as it doesn't know the number of items it has, so adding that line would let it know the count, but it seems that it's not solving the problem completely. I printed "imagesCount", for the first 3 insert operations it's always 1, suddenly it jumped to 4, after that the insert produces the crash. Any idea why that happens?

            – Dania
            Jan 2 at 11:36













          • Ah, that is good to know, I was not aware of this bug. Regarding your problem: It might be caused by a race condition when adding data to the DB in the background, but it's hard to say without actually trying it, so it's just a guess. My recommendation is to 1) move all the DB code out of the VC for a cleaner architecture and 2) cache the items one every change (insert/delete) to a property so that there is a central "truth" for the content of your collectionview at any given moment. Right now every callback method makes its own call to the DB and we don't know if the result is always the same

            – Robin Bork
            Jan 2 at 14:14











          • Another observation: I see that you have code for adding and removing sections in your collection view as well. Is that being called? In your numberOfItemsInSection you make no distinction between the different sections as far as I can see that might also result to wrong counts.

            – Robin Bork
            Jan 2 at 14:14











          • thanks for the help. Yes, you're right about the section updates that wasn't needed. I could finally solve both problems, I posted an answer about that, you can check it. Thanks again

            – Dania
            Jan 3 at 19:31



















          1














          You have a common problem with UICollectionView inconsistency during batching update.
          If you perform deletion/adding new items in the incorrect order UICollectionView might crash.
          This problem has 2 typical solutions:




          1. use -reloadData() instead of batch updates.

          2. use third party libraries with safe implementation of batch update. Smth like this https://github.com/badoo/ios-collection-batch-updates






          share|improve this answer































            1














            The problem is NSFetchedResultsController should only use a main thread NSManagedObjectContext.



            Solution: create two NSManagedObjectContext objects, one in the main thread for NSFetchedResultsController and one in the background thread for performing the data writing.



            let writeContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
            let readContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
            let fetchedController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: readContext, sectionNameKeyPath: nil, cacheName: nil)
            writeContext.parent = readContext



            UICollectionView will be updated properly once the data is saved in the writeContext with the following chain:




            writeContext(background thread ) -> readContext(main thread) -> NSFetchedResultsController (main thread) -> UICollectionView (main thread)







            share|improve this answer































              0














              I would like to thank Robin Bork, Eugene El, and meim for their answers.



              I could finally solve both issues.



              For the CollectionView problem, I felt like I was updating it too many times, as you can see in the code, I used to update it in two FetchedResultsController delegate methods, and also through an observer that observes any changes on the context. So I removed all of that and just used this method:



              func controllerWillChangeContent(_ controller: 

              NSFetchedResultsController<NSFetchRequestResult>) {
              DispatchQueue.main.async {
              self.collectionView.reloadData()
              }
              }


              In addition to that, CollectionView has a bug in maintaining the items count in a section sometimes as Eugene El mentioned. So, I just used reloadData to update its items and that worked well, I removed the usage of any method that adjusts its items item by item like inserting an item at a specific IndexPath.



              For the dangling object problem. As you can see from the code, I had a Location object and an Image object. My location object was already filled with a location and it was coming from view context, so I just needed to create a corresponding object from it using its ID (as you see in the code in the question).



              The problem was in the image object, I was creating an object on the view context (which contains no data inserted), get its ID, then build a corresponding object on the background context. After reading about this error and thinking about my code, I thought that the reason maybe because the Image object on the view context didn't contain any data. So, I removed the code that creates that object on the view context and created a one directly on the background context and used it as in the code below, and it worked!



              func saveImagesToDb () {

              //Store the image in the DB along with its location on the background thread
              dataController.backgroundContext.perform {
              for downloadedImage in self.downloadedImages {
              let imageOnBackgroundContext = Image (context: self.dataController.backgroundContext)

              //imagesLocation is on the view context
              let locationObjectId = self.imagesLocation.objectID
              let locationOnBackgroundContext = self.dataController.backgroundContext.object(with: locationObjectId) as! Location

              let imageData = NSData (data: downloadedImage.jpegData(compressionQuality: 0.5)!)
              imageOnBackgroundContext.image = imageData as Data
              imageOnBackgroundContext.location = locationOnBackgroundContext


              guard (try? self.dataController.backgroundContext.save ()) != nil else {
              self.showAlert("Saving Error", "Couldn't store images in Database")
              return
              }
              }
              }

              }


              If anyone has another thought different from what I said about why the first method that first creates an empty Image object on the view context, then creates a corresponding one on the background context didn't work, please let us know.






              share|improve this answer























                Your Answer






                StackExchange.ifUsing("editor", function () {
                StackExchange.using("externalEditor", function () {
                StackExchange.using("snippets", function () {
                StackExchange.snippets.init();
                });
                });
                }, "code-snippets");

                StackExchange.ready(function() {
                var channelOptions = {
                tags: "".split(" "),
                id: "1"
                };
                initTagRenderer("".split(" "), "".split(" "), channelOptions);

                StackExchange.using("externalEditor", function() {
                // Have to fire editor after snippets, if snippets enabled
                if (StackExchange.settings.snippets.snippetsEnabled) {
                StackExchange.using("snippets", function() {
                createEditor();
                });
                }
                else {
                createEditor();
                }
                });

                function createEditor() {
                StackExchange.prepareEditor({
                heartbeatType: 'answer',
                autoActivateHeartbeat: false,
                convertImagesToLinks: true,
                noModals: true,
                showLowRepImageUploadWarning: true,
                reputationToPostImages: 10,
                bindNavPrevention: true,
                postfix: "",
                imageUploader: {
                brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
                contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
                allowUrls: true
                },
                onDemand: true,
                discardSelector: ".discard-answer"
                ,immediatelyShowMarkdownHelp:true
                });


                }
                });














                draft saved

                draft discarded


















                StackExchange.ready(
                function () {
                StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54004589%2fproblems-saving-nsmanagedobjects-on-a-background-context%23new-answer', 'question_page');
                }
                );

                Post as a guest















                Required, but never shown

























                4 Answers
                4






                active

                oldest

                votes








                4 Answers
                4






                active

                oldest

                votes









                active

                oldest

                votes






                active

                oldest

                votes









                1














                I cannot tell you what the problem is with 1), but I think 2) is not (just) a problem with the database.



                The error your are getting usually happens when you add or remove items/sections to a collectionview, but when numberOfItemsInSection is called afterwards the numbers don't add up. Example: you have 5 items and add 2, but then numberOfItemsInSection is called and returns 6, which creates the inconsistency.



                In your case my guess would be that you add items with collectionView.insertItems(), but this line returns 0 afterwards:



                guard let imagesCount = fetchedResultsController.fetchedObjects?.count else {return 0}


                What also confused me in your code are these parts:



                 DispatchQueue.main.async {
                print ("calling section items")
                self.collectionView!.numberOfItems(inSection: 0)
                self.collectionView.insertItems(at: [newIndexPath!])
                }


                You are requesting the number of items there, but you don't actually do anything with the result of the function. Is there a reason for that?



                Even though I don't know what the CoreData issue is I would advise you to not access the DB in the tableview delegate methods, but to have an array of items that is fetched once and is updated only when the db content changes. That is probably more performant and a lot easier to maintain.






                share|improve this answer
























                • Thanks @Robin Bork. As for the line that looks confusing, I used it based on Michael Su answer here: stackoverflow.com/questions/19199985/…. The discussion states that the problem is from a bug in Collection View as it doesn't know the number of items it has, so adding that line would let it know the count, but it seems that it's not solving the problem completely. I printed "imagesCount", for the first 3 insert operations it's always 1, suddenly it jumped to 4, after that the insert produces the crash. Any idea why that happens?

                  – Dania
                  Jan 2 at 11:36













                • Ah, that is good to know, I was not aware of this bug. Regarding your problem: It might be caused by a race condition when adding data to the DB in the background, but it's hard to say without actually trying it, so it's just a guess. My recommendation is to 1) move all the DB code out of the VC for a cleaner architecture and 2) cache the items one every change (insert/delete) to a property so that there is a central "truth" for the content of your collectionview at any given moment. Right now every callback method makes its own call to the DB and we don't know if the result is always the same

                  – Robin Bork
                  Jan 2 at 14:14











                • Another observation: I see that you have code for adding and removing sections in your collection view as well. Is that being called? In your numberOfItemsInSection you make no distinction between the different sections as far as I can see that might also result to wrong counts.

                  – Robin Bork
                  Jan 2 at 14:14











                • thanks for the help. Yes, you're right about the section updates that wasn't needed. I could finally solve both problems, I posted an answer about that, you can check it. Thanks again

                  – Dania
                  Jan 3 at 19:31
















                1














                I cannot tell you what the problem is with 1), but I think 2) is not (just) a problem with the database.



                The error your are getting usually happens when you add or remove items/sections to a collectionview, but when numberOfItemsInSection is called afterwards the numbers don't add up. Example: you have 5 items and add 2, but then numberOfItemsInSection is called and returns 6, which creates the inconsistency.



                In your case my guess would be that you add items with collectionView.insertItems(), but this line returns 0 afterwards:



                guard let imagesCount = fetchedResultsController.fetchedObjects?.count else {return 0}


                What also confused me in your code are these parts:



                 DispatchQueue.main.async {
                print ("calling section items")
                self.collectionView!.numberOfItems(inSection: 0)
                self.collectionView.insertItems(at: [newIndexPath!])
                }


                You are requesting the number of items there, but you don't actually do anything with the result of the function. Is there a reason for that?



                Even though I don't know what the CoreData issue is I would advise you to not access the DB in the tableview delegate methods, but to have an array of items that is fetched once and is updated only when the db content changes. That is probably more performant and a lot easier to maintain.






                share|improve this answer
























                • Thanks @Robin Bork. As for the line that looks confusing, I used it based on Michael Su answer here: stackoverflow.com/questions/19199985/…. The discussion states that the problem is from a bug in Collection View as it doesn't know the number of items it has, so adding that line would let it know the count, but it seems that it's not solving the problem completely. I printed "imagesCount", for the first 3 insert operations it's always 1, suddenly it jumped to 4, after that the insert produces the crash. Any idea why that happens?

                  – Dania
                  Jan 2 at 11:36













                • Ah, that is good to know, I was not aware of this bug. Regarding your problem: It might be caused by a race condition when adding data to the DB in the background, but it's hard to say without actually trying it, so it's just a guess. My recommendation is to 1) move all the DB code out of the VC for a cleaner architecture and 2) cache the items one every change (insert/delete) to a property so that there is a central "truth" for the content of your collectionview at any given moment. Right now every callback method makes its own call to the DB and we don't know if the result is always the same

                  – Robin Bork
                  Jan 2 at 14:14











                • Another observation: I see that you have code for adding and removing sections in your collection view as well. Is that being called? In your numberOfItemsInSection you make no distinction between the different sections as far as I can see that might also result to wrong counts.

                  – Robin Bork
                  Jan 2 at 14:14











                • thanks for the help. Yes, you're right about the section updates that wasn't needed. I could finally solve both problems, I posted an answer about that, you can check it. Thanks again

                  – Dania
                  Jan 3 at 19:31














                1












                1








                1







                I cannot tell you what the problem is with 1), but I think 2) is not (just) a problem with the database.



                The error your are getting usually happens when you add or remove items/sections to a collectionview, but when numberOfItemsInSection is called afterwards the numbers don't add up. Example: you have 5 items and add 2, but then numberOfItemsInSection is called and returns 6, which creates the inconsistency.



                In your case my guess would be that you add items with collectionView.insertItems(), but this line returns 0 afterwards:



                guard let imagesCount = fetchedResultsController.fetchedObjects?.count else {return 0}


                What also confused me in your code are these parts:



                 DispatchQueue.main.async {
                print ("calling section items")
                self.collectionView!.numberOfItems(inSection: 0)
                self.collectionView.insertItems(at: [newIndexPath!])
                }


                You are requesting the number of items there, but you don't actually do anything with the result of the function. Is there a reason for that?



                Even though I don't know what the CoreData issue is I would advise you to not access the DB in the tableview delegate methods, but to have an array of items that is fetched once and is updated only when the db content changes. That is probably more performant and a lot easier to maintain.






                share|improve this answer













                I cannot tell you what the problem is with 1), but I think 2) is not (just) a problem with the database.



                The error your are getting usually happens when you add or remove items/sections to a collectionview, but when numberOfItemsInSection is called afterwards the numbers don't add up. Example: you have 5 items and add 2, but then numberOfItemsInSection is called and returns 6, which creates the inconsistency.



                In your case my guess would be that you add items with collectionView.insertItems(), but this line returns 0 afterwards:



                guard let imagesCount = fetchedResultsController.fetchedObjects?.count else {return 0}


                What also confused me in your code are these parts:



                 DispatchQueue.main.async {
                print ("calling section items")
                self.collectionView!.numberOfItems(inSection: 0)
                self.collectionView.insertItems(at: [newIndexPath!])
                }


                You are requesting the number of items there, but you don't actually do anything with the result of the function. Is there a reason for that?



                Even though I don't know what the CoreData issue is I would advise you to not access the DB in the tableview delegate methods, but to have an array of items that is fetched once and is updated only when the db content changes. That is probably more performant and a lot easier to maintain.







                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Jan 2 at 11:14









                Robin BorkRobin Bork

                1326




                1326













                • Thanks @Robin Bork. As for the line that looks confusing, I used it based on Michael Su answer here: stackoverflow.com/questions/19199985/…. The discussion states that the problem is from a bug in Collection View as it doesn't know the number of items it has, so adding that line would let it know the count, but it seems that it's not solving the problem completely. I printed "imagesCount", for the first 3 insert operations it's always 1, suddenly it jumped to 4, after that the insert produces the crash. Any idea why that happens?

                  – Dania
                  Jan 2 at 11:36













                • Ah, that is good to know, I was not aware of this bug. Regarding your problem: It might be caused by a race condition when adding data to the DB in the background, but it's hard to say without actually trying it, so it's just a guess. My recommendation is to 1) move all the DB code out of the VC for a cleaner architecture and 2) cache the items one every change (insert/delete) to a property so that there is a central "truth" for the content of your collectionview at any given moment. Right now every callback method makes its own call to the DB and we don't know if the result is always the same

                  – Robin Bork
                  Jan 2 at 14:14











                • Another observation: I see that you have code for adding and removing sections in your collection view as well. Is that being called? In your numberOfItemsInSection you make no distinction between the different sections as far as I can see that might also result to wrong counts.

                  – Robin Bork
                  Jan 2 at 14:14











                • thanks for the help. Yes, you're right about the section updates that wasn't needed. I could finally solve both problems, I posted an answer about that, you can check it. Thanks again

                  – Dania
                  Jan 3 at 19:31



















                • Thanks @Robin Bork. As for the line that looks confusing, I used it based on Michael Su answer here: stackoverflow.com/questions/19199985/…. The discussion states that the problem is from a bug in Collection View as it doesn't know the number of items it has, so adding that line would let it know the count, but it seems that it's not solving the problem completely. I printed "imagesCount", for the first 3 insert operations it's always 1, suddenly it jumped to 4, after that the insert produces the crash. Any idea why that happens?

                  – Dania
                  Jan 2 at 11:36













                • Ah, that is good to know, I was not aware of this bug. Regarding your problem: It might be caused by a race condition when adding data to the DB in the background, but it's hard to say without actually trying it, so it's just a guess. My recommendation is to 1) move all the DB code out of the VC for a cleaner architecture and 2) cache the items one every change (insert/delete) to a property so that there is a central "truth" for the content of your collectionview at any given moment. Right now every callback method makes its own call to the DB and we don't know if the result is always the same

                  – Robin Bork
                  Jan 2 at 14:14











                • Another observation: I see that you have code for adding and removing sections in your collection view as well. Is that being called? In your numberOfItemsInSection you make no distinction between the different sections as far as I can see that might also result to wrong counts.

                  – Robin Bork
                  Jan 2 at 14:14











                • thanks for the help. Yes, you're right about the section updates that wasn't needed. I could finally solve both problems, I posted an answer about that, you can check it. Thanks again

                  – Dania
                  Jan 3 at 19:31

















                Thanks @Robin Bork. As for the line that looks confusing, I used it based on Michael Su answer here: stackoverflow.com/questions/19199985/…. The discussion states that the problem is from a bug in Collection View as it doesn't know the number of items it has, so adding that line would let it know the count, but it seems that it's not solving the problem completely. I printed "imagesCount", for the first 3 insert operations it's always 1, suddenly it jumped to 4, after that the insert produces the crash. Any idea why that happens?

                – Dania
                Jan 2 at 11:36







                Thanks @Robin Bork. As for the line that looks confusing, I used it based on Michael Su answer here: stackoverflow.com/questions/19199985/…. The discussion states that the problem is from a bug in Collection View as it doesn't know the number of items it has, so adding that line would let it know the count, but it seems that it's not solving the problem completely. I printed "imagesCount", for the first 3 insert operations it's always 1, suddenly it jumped to 4, after that the insert produces the crash. Any idea why that happens?

                – Dania
                Jan 2 at 11:36















                Ah, that is good to know, I was not aware of this bug. Regarding your problem: It might be caused by a race condition when adding data to the DB in the background, but it's hard to say without actually trying it, so it's just a guess. My recommendation is to 1) move all the DB code out of the VC for a cleaner architecture and 2) cache the items one every change (insert/delete) to a property so that there is a central "truth" for the content of your collectionview at any given moment. Right now every callback method makes its own call to the DB and we don't know if the result is always the same

                – Robin Bork
                Jan 2 at 14:14





                Ah, that is good to know, I was not aware of this bug. Regarding your problem: It might be caused by a race condition when adding data to the DB in the background, but it's hard to say without actually trying it, so it's just a guess. My recommendation is to 1) move all the DB code out of the VC for a cleaner architecture and 2) cache the items one every change (insert/delete) to a property so that there is a central "truth" for the content of your collectionview at any given moment. Right now every callback method makes its own call to the DB and we don't know if the result is always the same

                – Robin Bork
                Jan 2 at 14:14













                Another observation: I see that you have code for adding and removing sections in your collection view as well. Is that being called? In your numberOfItemsInSection you make no distinction between the different sections as far as I can see that might also result to wrong counts.

                – Robin Bork
                Jan 2 at 14:14





                Another observation: I see that you have code for adding and removing sections in your collection view as well. Is that being called? In your numberOfItemsInSection you make no distinction between the different sections as far as I can see that might also result to wrong counts.

                – Robin Bork
                Jan 2 at 14:14













                thanks for the help. Yes, you're right about the section updates that wasn't needed. I could finally solve both problems, I posted an answer about that, you can check it. Thanks again

                – Dania
                Jan 3 at 19:31





                thanks for the help. Yes, you're right about the section updates that wasn't needed. I could finally solve both problems, I posted an answer about that, you can check it. Thanks again

                – Dania
                Jan 3 at 19:31













                1














                You have a common problem with UICollectionView inconsistency during batching update.
                If you perform deletion/adding new items in the incorrect order UICollectionView might crash.
                This problem has 2 typical solutions:




                1. use -reloadData() instead of batch updates.

                2. use third party libraries with safe implementation of batch update. Smth like this https://github.com/badoo/ios-collection-batch-updates






                share|improve this answer




























                  1














                  You have a common problem with UICollectionView inconsistency during batching update.
                  If you perform deletion/adding new items in the incorrect order UICollectionView might crash.
                  This problem has 2 typical solutions:




                  1. use -reloadData() instead of batch updates.

                  2. use third party libraries with safe implementation of batch update. Smth like this https://github.com/badoo/ios-collection-batch-updates






                  share|improve this answer


























                    1












                    1








                    1







                    You have a common problem with UICollectionView inconsistency during batching update.
                    If you perform deletion/adding new items in the incorrect order UICollectionView might crash.
                    This problem has 2 typical solutions:




                    1. use -reloadData() instead of batch updates.

                    2. use third party libraries with safe implementation of batch update. Smth like this https://github.com/badoo/ios-collection-batch-updates






                    share|improve this answer













                    You have a common problem with UICollectionView inconsistency during batching update.
                    If you perform deletion/adding new items in the incorrect order UICollectionView might crash.
                    This problem has 2 typical solutions:




                    1. use -reloadData() instead of batch updates.

                    2. use third party libraries with safe implementation of batch update. Smth like this https://github.com/badoo/ios-collection-batch-updates







                    share|improve this answer












                    share|improve this answer



                    share|improve this answer










                    answered Jan 2 at 14:33









                    Eugene ElEugene El

                    613




                    613























                        1














                        The problem is NSFetchedResultsController should only use a main thread NSManagedObjectContext.



                        Solution: create two NSManagedObjectContext objects, one in the main thread for NSFetchedResultsController and one in the background thread for performing the data writing.



                        let writeContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
                        let readContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
                        let fetchedController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: readContext, sectionNameKeyPath: nil, cacheName: nil)
                        writeContext.parent = readContext



                        UICollectionView will be updated properly once the data is saved in the writeContext with the following chain:




                        writeContext(background thread ) -> readContext(main thread) -> NSFetchedResultsController (main thread) -> UICollectionView (main thread)







                        share|improve this answer




























                          1














                          The problem is NSFetchedResultsController should only use a main thread NSManagedObjectContext.



                          Solution: create two NSManagedObjectContext objects, one in the main thread for NSFetchedResultsController and one in the background thread for performing the data writing.



                          let writeContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
                          let readContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
                          let fetchedController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: readContext, sectionNameKeyPath: nil, cacheName: nil)
                          writeContext.parent = readContext



                          UICollectionView will be updated properly once the data is saved in the writeContext with the following chain:




                          writeContext(background thread ) -> readContext(main thread) -> NSFetchedResultsController (main thread) -> UICollectionView (main thread)







                          share|improve this answer


























                            1












                            1








                            1







                            The problem is NSFetchedResultsController should only use a main thread NSManagedObjectContext.



                            Solution: create two NSManagedObjectContext objects, one in the main thread for NSFetchedResultsController and one in the background thread for performing the data writing.



                            let writeContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
                            let readContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
                            let fetchedController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: readContext, sectionNameKeyPath: nil, cacheName: nil)
                            writeContext.parent = readContext



                            UICollectionView will be updated properly once the data is saved in the writeContext with the following chain:




                            writeContext(background thread ) -> readContext(main thread) -> NSFetchedResultsController (main thread) -> UICollectionView (main thread)







                            share|improve this answer













                            The problem is NSFetchedResultsController should only use a main thread NSManagedObjectContext.



                            Solution: create two NSManagedObjectContext objects, one in the main thread for NSFetchedResultsController and one in the background thread for performing the data writing.



                            let writeContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
                            let readContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
                            let fetchedController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: readContext, sectionNameKeyPath: nil, cacheName: nil)
                            writeContext.parent = readContext



                            UICollectionView will be updated properly once the data is saved in the writeContext with the following chain:




                            writeContext(background thread ) -> readContext(main thread) -> NSFetchedResultsController (main thread) -> UICollectionView (main thread)








                            share|improve this answer












                            share|improve this answer



                            share|improve this answer










                            answered Jan 3 at 5:01









                            meimmeim

                            69237




                            69237























                                0














                                I would like to thank Robin Bork, Eugene El, and meim for their answers.



                                I could finally solve both issues.



                                For the CollectionView problem, I felt like I was updating it too many times, as you can see in the code, I used to update it in two FetchedResultsController delegate methods, and also through an observer that observes any changes on the context. So I removed all of that and just used this method:



                                func controllerWillChangeContent(_ controller: 

                                NSFetchedResultsController<NSFetchRequestResult>) {
                                DispatchQueue.main.async {
                                self.collectionView.reloadData()
                                }
                                }


                                In addition to that, CollectionView has a bug in maintaining the items count in a section sometimes as Eugene El mentioned. So, I just used reloadData to update its items and that worked well, I removed the usage of any method that adjusts its items item by item like inserting an item at a specific IndexPath.



                                For the dangling object problem. As you can see from the code, I had a Location object and an Image object. My location object was already filled with a location and it was coming from view context, so I just needed to create a corresponding object from it using its ID (as you see in the code in the question).



                                The problem was in the image object, I was creating an object on the view context (which contains no data inserted), get its ID, then build a corresponding object on the background context. After reading about this error and thinking about my code, I thought that the reason maybe because the Image object on the view context didn't contain any data. So, I removed the code that creates that object on the view context and created a one directly on the background context and used it as in the code below, and it worked!



                                func saveImagesToDb () {

                                //Store the image in the DB along with its location on the background thread
                                dataController.backgroundContext.perform {
                                for downloadedImage in self.downloadedImages {
                                let imageOnBackgroundContext = Image (context: self.dataController.backgroundContext)

                                //imagesLocation is on the view context
                                let locationObjectId = self.imagesLocation.objectID
                                let locationOnBackgroundContext = self.dataController.backgroundContext.object(with: locationObjectId) as! Location

                                let imageData = NSData (data: downloadedImage.jpegData(compressionQuality: 0.5)!)
                                imageOnBackgroundContext.image = imageData as Data
                                imageOnBackgroundContext.location = locationOnBackgroundContext


                                guard (try? self.dataController.backgroundContext.save ()) != nil else {
                                self.showAlert("Saving Error", "Couldn't store images in Database")
                                return
                                }
                                }
                                }

                                }


                                If anyone has another thought different from what I said about why the first method that first creates an empty Image object on the view context, then creates a corresponding one on the background context didn't work, please let us know.






                                share|improve this answer




























                                  0














                                  I would like to thank Robin Bork, Eugene El, and meim for their answers.



                                  I could finally solve both issues.



                                  For the CollectionView problem, I felt like I was updating it too many times, as you can see in the code, I used to update it in two FetchedResultsController delegate methods, and also through an observer that observes any changes on the context. So I removed all of that and just used this method:



                                  func controllerWillChangeContent(_ controller: 

                                  NSFetchedResultsController<NSFetchRequestResult>) {
                                  DispatchQueue.main.async {
                                  self.collectionView.reloadData()
                                  }
                                  }


                                  In addition to that, CollectionView has a bug in maintaining the items count in a section sometimes as Eugene El mentioned. So, I just used reloadData to update its items and that worked well, I removed the usage of any method that adjusts its items item by item like inserting an item at a specific IndexPath.



                                  For the dangling object problem. As you can see from the code, I had a Location object and an Image object. My location object was already filled with a location and it was coming from view context, so I just needed to create a corresponding object from it using its ID (as you see in the code in the question).



                                  The problem was in the image object, I was creating an object on the view context (which contains no data inserted), get its ID, then build a corresponding object on the background context. After reading about this error and thinking about my code, I thought that the reason maybe because the Image object on the view context didn't contain any data. So, I removed the code that creates that object on the view context and created a one directly on the background context and used it as in the code below, and it worked!



                                  func saveImagesToDb () {

                                  //Store the image in the DB along with its location on the background thread
                                  dataController.backgroundContext.perform {
                                  for downloadedImage in self.downloadedImages {
                                  let imageOnBackgroundContext = Image (context: self.dataController.backgroundContext)

                                  //imagesLocation is on the view context
                                  let locationObjectId = self.imagesLocation.objectID
                                  let locationOnBackgroundContext = self.dataController.backgroundContext.object(with: locationObjectId) as! Location

                                  let imageData = NSData (data: downloadedImage.jpegData(compressionQuality: 0.5)!)
                                  imageOnBackgroundContext.image = imageData as Data
                                  imageOnBackgroundContext.location = locationOnBackgroundContext


                                  guard (try? self.dataController.backgroundContext.save ()) != nil else {
                                  self.showAlert("Saving Error", "Couldn't store images in Database")
                                  return
                                  }
                                  }
                                  }

                                  }


                                  If anyone has another thought different from what I said about why the first method that first creates an empty Image object on the view context, then creates a corresponding one on the background context didn't work, please let us know.






                                  share|improve this answer


























                                    0












                                    0








                                    0







                                    I would like to thank Robin Bork, Eugene El, and meim for their answers.



                                    I could finally solve both issues.



                                    For the CollectionView problem, I felt like I was updating it too many times, as you can see in the code, I used to update it in two FetchedResultsController delegate methods, and also through an observer that observes any changes on the context. So I removed all of that and just used this method:



                                    func controllerWillChangeContent(_ controller: 

                                    NSFetchedResultsController<NSFetchRequestResult>) {
                                    DispatchQueue.main.async {
                                    self.collectionView.reloadData()
                                    }
                                    }


                                    In addition to that, CollectionView has a bug in maintaining the items count in a section sometimes as Eugene El mentioned. So, I just used reloadData to update its items and that worked well, I removed the usage of any method that adjusts its items item by item like inserting an item at a specific IndexPath.



                                    For the dangling object problem. As you can see from the code, I had a Location object and an Image object. My location object was already filled with a location and it was coming from view context, so I just needed to create a corresponding object from it using its ID (as you see in the code in the question).



                                    The problem was in the image object, I was creating an object on the view context (which contains no data inserted), get its ID, then build a corresponding object on the background context. After reading about this error and thinking about my code, I thought that the reason maybe because the Image object on the view context didn't contain any data. So, I removed the code that creates that object on the view context and created a one directly on the background context and used it as in the code below, and it worked!



                                    func saveImagesToDb () {

                                    //Store the image in the DB along with its location on the background thread
                                    dataController.backgroundContext.perform {
                                    for downloadedImage in self.downloadedImages {
                                    let imageOnBackgroundContext = Image (context: self.dataController.backgroundContext)

                                    //imagesLocation is on the view context
                                    let locationObjectId = self.imagesLocation.objectID
                                    let locationOnBackgroundContext = self.dataController.backgroundContext.object(with: locationObjectId) as! Location

                                    let imageData = NSData (data: downloadedImage.jpegData(compressionQuality: 0.5)!)
                                    imageOnBackgroundContext.image = imageData as Data
                                    imageOnBackgroundContext.location = locationOnBackgroundContext


                                    guard (try? self.dataController.backgroundContext.save ()) != nil else {
                                    self.showAlert("Saving Error", "Couldn't store images in Database")
                                    return
                                    }
                                    }
                                    }

                                    }


                                    If anyone has another thought different from what I said about why the first method that first creates an empty Image object on the view context, then creates a corresponding one on the background context didn't work, please let us know.






                                    share|improve this answer













                                    I would like to thank Robin Bork, Eugene El, and meim for their answers.



                                    I could finally solve both issues.



                                    For the CollectionView problem, I felt like I was updating it too many times, as you can see in the code, I used to update it in two FetchedResultsController delegate methods, and also through an observer that observes any changes on the context. So I removed all of that and just used this method:



                                    func controllerWillChangeContent(_ controller: 

                                    NSFetchedResultsController<NSFetchRequestResult>) {
                                    DispatchQueue.main.async {
                                    self.collectionView.reloadData()
                                    }
                                    }


                                    In addition to that, CollectionView has a bug in maintaining the items count in a section sometimes as Eugene El mentioned. So, I just used reloadData to update its items and that worked well, I removed the usage of any method that adjusts its items item by item like inserting an item at a specific IndexPath.



                                    For the dangling object problem. As you can see from the code, I had a Location object and an Image object. My location object was already filled with a location and it was coming from view context, so I just needed to create a corresponding object from it using its ID (as you see in the code in the question).



                                    The problem was in the image object, I was creating an object on the view context (which contains no data inserted), get its ID, then build a corresponding object on the background context. After reading about this error and thinking about my code, I thought that the reason maybe because the Image object on the view context didn't contain any data. So, I removed the code that creates that object on the view context and created a one directly on the background context and used it as in the code below, and it worked!



                                    func saveImagesToDb () {

                                    //Store the image in the DB along with its location on the background thread
                                    dataController.backgroundContext.perform {
                                    for downloadedImage in self.downloadedImages {
                                    let imageOnBackgroundContext = Image (context: self.dataController.backgroundContext)

                                    //imagesLocation is on the view context
                                    let locationObjectId = self.imagesLocation.objectID
                                    let locationOnBackgroundContext = self.dataController.backgroundContext.object(with: locationObjectId) as! Location

                                    let imageData = NSData (data: downloadedImage.jpegData(compressionQuality: 0.5)!)
                                    imageOnBackgroundContext.image = imageData as Data
                                    imageOnBackgroundContext.location = locationOnBackgroundContext


                                    guard (try? self.dataController.backgroundContext.save ()) != nil else {
                                    self.showAlert("Saving Error", "Couldn't store images in Database")
                                    return
                                    }
                                    }
                                    }

                                    }


                                    If anyone has another thought different from what I said about why the first method that first creates an empty Image object on the view context, then creates a corresponding one on the background context didn't work, please let us know.







                                    share|improve this answer












                                    share|improve this answer



                                    share|improve this answer










                                    answered Jan 3 at 19:29









                                    DaniaDania

                                    80521538




                                    80521538






























                                        draft saved

                                        draft discarded




















































                                        Thanks for contributing an answer to Stack Overflow!


                                        • Please be sure to answer the question. Provide details and share your research!

                                        But avoid



                                        • Asking for help, clarification, or responding to other answers.

                                        • Making statements based on opinion; back them up with references or personal experience.


                                        To learn more, see our tips on writing great answers.




                                        draft saved


                                        draft discarded














                                        StackExchange.ready(
                                        function () {
                                        StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54004589%2fproblems-saving-nsmanagedobjects-on-a-background-context%23new-answer', 'question_page');
                                        }
                                        );

                                        Post as a guest















                                        Required, but never shown





















































                                        Required, but never shown














                                        Required, but never shown












                                        Required, but never shown







                                        Required, but never shown

































                                        Required, but never shown














                                        Required, but never shown












                                        Required, but never shown







                                        Required, but never shown







                                        Popular posts from this blog

                                        MongoDB - Not Authorized To Execute Command

                                        How to fix TextFormField cause rebuild widget in Flutter

                                        in spring boot 2.1 many test slices are not allowed anymore due to multiple @BootstrapWith