0
votes

Problèmes d'économie de nsmanagedObjects sur un contexte de fond

Je me débats avec cela pendant des jours. Je vais apprécier toute aide.

J'ai un emplacement nsmanagedObject code> et une image nsmanagedObject code>, ils ont une relation à une autre, c'est-à-dire un emplacement possède de nombreuses images. P>

J'ai 2 écrans, dans la première, l'utilisateur ajoute des emplacements sur le contexte de la vue et ils sont ajoutés et récupérés sans problèmes. P>

Maintenant, dans le deuxième écran, je souhaite récupérer des images en fonction de l'emplacement sélectionné dans le premier écran, puis affichez les images dans une vue de collection. Les images sont d'abord récupérées de Flickr, puis enregistrées dans la DB. P>

Je veux enregistrer et récupérer des images sur un contexte d'arrière-plan et cela me provoque beaucoup de problèmes. p>

  1. Lorsque j'essaie d'enregistrer chaque image extraite de Flickr, je reçois un avertissement indiquant qu'il existe un objet pendling et la relation peut être établie: li> ol>

    Ceci est mon code d'économie: p> xxx pré>

    Comme vous pouvez le constater dans le code ci-dessus, je bâtiment nsmanagedObject sur le contexte d'arrière-plan en fonction de l'ID récupéré en fonction de l'ID récupéré de ceux sur le contexte de la vue. Chaque fois que sauveimagestodb code> est appelé i obtenir l'avertissement, alors quel est le problème? p>

    1. En dépit de l'avertissement ci-dessus, lorsque je récupère les données via une fetchedResultController (qui fonctionne sur le contexte de l'arrière-plan). La vue de la collection Voir parfois les images juste bien et parfois, je reçois cette erreur: Li> ol>

      application de terminaison en raison d'une exception non capturée 'NsinternalinconsiscyException', raison: "Mise à jour non valide: Nombre non valide d'éléments à la section 0. Le nombre d'éléments contenus dans une section existante après la mise à jour (4) doit être égal au nombre d'éléments contenus dans cette section avant la mise à jour (1), plus ou moins le nombre d'éléments insérés ou supprimés de cette section (1 inséré, 0 supprimé) et plus ou moins le nombre d'éléments déplacés dans ou en dehors. de cette section (0 déplacé, 0 déplacé). code> ' p>

      Voici quelques extraits de code liés à la configuration de la FetchedResultSluStroller et à la mise à jour de la vue de la collection en fonction des changements dans le contexte ou dans le fetchedResultSlugroller. P>

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


0 commentaires

4 Réponses :


1
votes

Je ne peux pas vous dire quel est le problème avec 1), mais je pense que 2) n'est pas (juste) un problème avec la base de données.

L'erreur que vous obtiendrez généralement lorsque vous ajoutez ou supprimez des éléments / des sections à une collectionView, mais lorsque NumberofitemSectionSection em> est appelé ensuite les chiffres ne s'additionnent pas. Exemple: vous avez 5 articles et ajoutez 2, mais NumberofitemSectionSection em> est appelé et renvoie 6, ce qui crée l'incohérence. P>

Dans votre cas, je suppose que vous ajouteriez des éléments avec collectionviev.insertitems () em>, mais cette ligne renvoie 0 après: p>

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


4 commentaires

Merci @robin Bork. Quant à la ligne qui semble déroutante, je l'ai utilisé sur la base de Michael SU République ici: Stackoverflow.com/questions/19199985/... . La discussion indique que le problème provient d'un bogue de la vue de la collection, car il ne connaît pas le nombre d'articles qu'il a, l'ajout de cette ligne le laisserait savoir le compte, mais il semble que cela ne résoudrait pas complètement le problème. J'ai imprimé "ImagesCount", pour les 3 premières opérations d'insertion, il est toujours 1, tout à coup, il a sauté à 4, après cela, l'insert produit l'accident. Une idée de cette idée?


Ah, c'est bon à savoir, je n'étais pas au courant de ce bogue. En ce qui concerne votre problème: cela pourrait être causé par une condition de course lors de l'ajout de données à la DB en arrière-plan, mais il est difficile de dire sans l'essayer, donc c'est juste une supposition. Ma recommandation est à 1) Déplacez tout le code DB hors de la VC pour une architecture de nettoyeuse et 2) cache les éléments une autre modification (insérer / supprimer) à une propriété afin qu'il existe une "vérité" centrale pour le contenu de votre Collectionview à tout moment. À l'heure actuelle, chaque méthode de rappel met son propre appel à la DB et nous ne savons pas si le résultat est toujours le même


Une autre observation: je vois que vous avez du code pour ajouter et supprimer des sections dans votre vue de collection. Est-ce que c'est appelé? Dans votre numéros de choix, vous ne faites aucune distinction entre les différentes sections, autant que je sache, cela pourrait également entraîner des chiffres erronés.


Merci pour l'aide. Oui, vous avez raison sur les mises à jour de la section qui n'était pas nécessaire. Je pourrais enfin résoudre les deux problèmes, j'ai posté une réponse à ce sujet, vous pouvez le vérifier. Merci encore



1
votes

Vous avez un problème commun avec uicollectionview incohérences lors de la mise à jour par lots. Si vous effectuez une suppression / l'ajout de nouveaux éléments dans l'ordre incorrect uicollectionview pourrait crash. Ce problème a 2 solutions typiques:

  1. Utilisez -ReloadData () au lieu de mises à jour de lots.
  2. Utilisez des bibliothèques tierces avec une mise en œuvre sûre de la mise à jour du lot. Smth comme ce https://github.com/badoo/ios-collection-batch-Updates

0 commentaires

1
votes

Le problème est NsfetchedResultSluTroller ne doit utiliser qu'un thread principal nsmanagedObjectContext.

SOLUTION : Créez deux objets NSManageDObjectContextContext, un dans le fil principal de NsfetchedResultSluTroller et un dans le fil d'arrière-plan pour effectuer la rédaction de données.

let writecontext = nsmanagedObjectContext (ConcurrencyType: .privateQueconCurencype) Laissez readcontext = nsmaniedObjectContext (ConcurrencyType: .MainQueconCurencyCurencyType) Laissez FetchedController = NsfetchedResultStructeur (Fetchrequest: Demande, GestionedObjectContext: ReadContext, SectionNameKathey: Nil, Cachename: Nil) writecontext.parent = readcontext

Uicollectionview sera mis à jour correctement une fois que les données sont enregistrées dans le Writecontext avec la chaîne suivante:

Writecontext (fil d'arrière-plan) -> readcontext (fil principal) -> NsfetchedResultController (fil principal) -> Uicollectionview (fil principal)


0 commentaires

0
votes

Je voudrais remercier Robin Bork, Eugene El et Meim pour leurs réponses.

Je pourrais enfin résoudre les deux problèmes.

Pour le problème de la collectionView, je me sentais comme si j'étais mis à jour trop de fois, comme vous pouvez le constater dans le code, je l'ai utilisé pour la mise à jour dans deux FetchedRésultsTRoller Méthodes de délégation, et également à travers un observateur qui observe tout changement sur le contexte. J'ai donc supprimé tout cela et simplement utiliser cette méthode: xxx

en plus de cela, CollectionView a un bogue dans la maintenance des articles dans une section parfois comme Eugene El mentionné. Donc, je viens d'utiliser ReloadData pour mettre à jour ses éléments et qui fonctionnait bien, j'ai supprimé l'utilisation de n'importe quelle méthode qui ajuste son élément d'éléments par élément comme l'insertion d'un élément à un indexpathique spécifique < p> pour le problème d'objet pendling. Comme vous pouvez le constater à partir du code, j'avais un objet de localisation et un objet d'image. Mon objet d'emplacement était déjà rempli d'un emplacement et venait de Contexte , alors j'ai juste besoin de créer un objet correspondant à partir de celui-ci à l'aide de son identifiant (comme vous le voyez dans le code de la question).

Le problème était dans l'objet image, je créais un objet sur le contexte (code> (qui ne contient aucune donnée insérée), obtenir son identifiant, puis construire un objet correspondant sur le contexte d'arrière-plan . Après avoir lu sur cette erreur et en pensant à mon code, je pensais que la raison peut-être parce que l'objet d'image sur le contexte ne contient aucune donnée. Donc, j'ai supprimé le code qui crée cet objet sur le contexte et a créé un directement sur le contexte d'arrière-plan et l'a utilisé comme dans le code ci-dessous, et cela a fonctionné! xxx

Si quelqu'un a une autre réflexion différente de ce que j'ai dit sur pourquoi la première méthode crée d'abord un objet d'image vide sur le contexte , puis Crée un correspondant sur le contexte d'arrière-plan n'a pas fonctionné, s'il vous plaît laissez-nous savoir.


0 commentaires