在后台上下文中保存NSManagedObjects的问题 [英] Problems saving NSManagedObjects on a background Context

查看:76
本文介绍了在后台上下文中保存NSManagedObjects的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我为此一直苦苦挣扎。我将不胜感激。

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

我有一个位置 NSManagedObject 和一个图像 NSManagedObject ,它们之间存在一对多的关系,即一个位置有很多图像。

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

我有2个屏幕,在第一个屏幕中,用户在视图上下文中添加位置,并且可以轻松地添加和检索位置。

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

现在,在第二个屏幕中,我要根据第一个屏幕中选择的位置检索图像,然后在集合视图中显示图像。首先从flickr检索图像,然后将其保存在DB中。

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. 当我尝试保存从flickr检索到的每张图像时,都会收到一条警告,指出存在悬空的对象并且可以建立关系:

这是我的保存代码:

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

如您在上面的代码中看到的,我正在基于从视图上下文中检索到的ID在后台上下文中构建NSManagedObject。每次调用 saveImagesToDb 时,我都会收到警告,那是什么问题呢?

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. 尽管有以上警告,当我通过FetchedResultsController(在后台上下文中工作)检索数据时。集合视图有时会很好地查看图像,有时会出现此错误:

由于未捕获而终止的应用程序异常 NSInternalInconsistencyException,原因:无效更新:第0节中的项目数无效。更新(4)之后现有节中包含的项目数必须等于更新前该节中包含的项目数( 1),加上或减去从该部分中插入或删除的项目数(插入1,删除0)和加上或减去移入或移出该部分的项目数(移入0,移出0)。'

以下代码段与设置FetchedResultsController以及根据上下文或FetchedResultsController的更新来更新Collection View有关。

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

我在做什么错了,我将不胜感激。 p>

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

推荐答案

我要感谢 Robin Bork,Eugene El和meim 的回答。

我终于可以解决两个问题。

I could finally solve both issues.

对于CollectionView问题,我觉得我对其进行了太多次更新,正如您在代码中所看到的那样,我过去曾两次将其更新为 FetchedResultsController 委托方法,并通过观察者观察上下文的任何变化。所以我删除了所有这些,只使用了这种方法:

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

此外,CollectionView还有一个错误,即有时会维护 Eugene El 中提到的部分中的项目计数。因此,我只是使用reloadData来更新其项目,并且效果很好,因此删除了使用逐项调整其项目的任何方法,例如在特定的 IndexPath 插入项目。

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.

用于悬空物体问题。从代码中可以看到,我有一个Location对象和一个Image对象。我的位置对象已经充满了位置,并且来自 view context ,因此我只需要使用其ID从该对象创建相应的对象(如您在问题中的代码)。

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).

问题出在图像对象中,我在 view上下文(其中不包含数据)上创建了一个对象插入),获取其ID,然后在背景上下文上构建相应的对象。在阅读了该错误并思考了我的代码之后,我认为原因可能是因为 view上下文上的Image对象不包含任何数据。因此,我删除了在视图上下文上创建该对象的代码,并直接在背景上下文上创建了一个对象,然后

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

    }

如果有人与我所说的想法不同,为什么第一个方法首先在 view上下文上创建一个空Image对象,然后创建背景上下文上的相应内容无效,请告诉我们。

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.

这篇关于在后台上下文中保存NSManagedObjects的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆