2016-06-15 64 views
6

Sono di fronte a un incubo di un arresto anomalo durante performBatchUpdates su una vista di raccolta.Nightmare with performBatchUpdates crash

Il problema è fondamentalmente questo: ho un sacco di immagini su una directory su un server. Voglio mostrare le miniature di quei file in una vista insieme. Ma la miniatura deve essere scaricata dal server in modo asincrono. All'arrivo verranno inseriti nella vista insieme utilizzando qualcosa come:

dispatch_async(dispatch_get_main_queue(), 
      ^{ 
       [self.collectionView performBatchUpdates:^{ 

       if (removedIndexes && [removedIndexes count] > 0) { 
        [self.collectionView deleteItemsAtIndexPaths:removedIndexes]; 
       } 

       if (changedIndexes && [changedIndexes count] > 0) { 
        [self.collectionView reloadItemsAtIndexPaths:changedIndexes]; 
       } 

       if (insertedIndexes && [insertedIndexes count] > 0) { 
        [self.collectionView insertItemsAtIndexPaths:insertedIndexes]; 
       } 

       } completion:nil]; 
      }); 

il problema è questo (penso). Supponiamo che a tempo = 0, la vista di raccolta abbia 10 elementi. Quindi aggiungo altri 100 file al server. L'applicazione vede i nuovi file e inizia a scaricare le miniature. Mentre le miniature vengono scaricate, verranno inserite nella vista raccolta. Ma poiché i download possono richiedere tempi diversi e questa operazione di download è asincrona, ad un certo punto iOS non tiene traccia del numero di elementi della raccolta e dell'intera cosa si bloccherà con questo messaggio infame catastrofico.

*** terminazione app causa eccezione non identificata 'NSInternalInconsistencyException', ragione: 'aggiornamento non valido: valido numero di elementi nella sezione 0. Il numero di elementi contenuti in una sezione esistente dopo l'aggiornamento (213) deve essere uguale al numero di articoli contenuti in quella sezione prima dell'aggiornamento (154), più o meno il numero di elementi inseriti o cancellati da quella sezione (40 inseriti, 0 cancellati) e più o meno il numero di elementi spostato in o fuori da quella sezione (0 spostato in, 0 spostato fuori). '

La prova che ho qualcosa di strano sta succedendo è che se stampo il conteggio degli elementi sul set di dati che vedo esattamente 213. Così, il set di dati corrisponde al numero corretto e il messaggio è una sciocchezza.

Ho avuto questo problema prima, here ma quello era un progetto iOS 7. In qualche modo il problema è tornato ora su iOS 8 e le soluzioni non funzionano e ora il set di dati IS IN SYNC.

risposta

1

Penso che il problema sia causato dagli indici.

chiave:

  • Per elementi aggiornati e cancellate, gli indici devono essere gli indici di elementi originali.
  • Per gli articoli inseriti, gli indici devono essere gli indici degli articoli finali.

Ecco un codice demo con i commenti:

class CollectionViewController: UICollectionViewController { 

    var items: [String]! 

    let before = ["To Be Deleted 1", "To Be Updated 1", "To Be Updated 2", "To Be Deleted 2", "Stay"] 
    let after = ["Updated 1", "Updated 2", "Added 1", "Stay", "Added 2"] 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Refresh", style: .Plain, target: self, action: #selector(CollectionViewController.onRefresh(_:))) 

     items = before 
    } 

    func onRefresh(_: AnyObject) { 

     items = after 

     collectionView?.performBatchUpdates({ 
      self.collectionView?.deleteItemsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0), NSIndexPath(forRow: 3, inSection: 0), ]) 

      // Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete and reload the same index path 
      // self.collectionView?.reloadItemsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0), NSIndexPath(forRow: 1, inSection: 0), ]) 

      // NOTE: Have to be the indexes of original list 
      self.collectionView?.reloadItemsAtIndexPaths([NSIndexPath(forRow: 1, inSection: 0), NSIndexPath(forRow: 2, inSection: 0), ]) 

      // Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert item 4 into section 0, but there are only 4 items in section 0 after the update' 
      // self.collectionView?.insertItemsAtIndexPaths([NSIndexPath(forRow: 4, inSection: 0), NSIndexPath(forRow: 5, inSection: 0), ]) 

      // NOTE: Have to be index of final list 
      self.collectionView?.insertItemsAtIndexPaths([NSIndexPath(forRow: 2, inSection: 0), NSIndexPath(forRow: 4, inSection: 0), ]) 

     }, completion: nil) 
    } 

    override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { 
     return 1 
    } 
    override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 
     return items.count 
    } 

    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 
     let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MyCell", forIndexPath: indexPath) 

     let label = cell.viewWithTag(100) as! UILabel 

     label.text = items[indexPath.row] 

     return cell 
    } 
} 
3

Sembra che sia necessario eseguire un po 'di lavoro aggiuntivo con il raggruppamento delle immagini che sono state visualizzate per ciascun gruppo di animazione. Da trattare con incidenti come questo prima, il modo in cui le opere 'performBatchUpdates' è

  1. Prima di invocare il tuo blocco, controlla doppie tutti i conteggi voce e li salva chiamando 'numberOfItemsInSection' (questo è il 154 nel vostro messaggio di errore).
  2. Esegue il blocco, tenendo traccia degli inserimenti/eliminazioni e calcola quale numero finale di articoli deve essere basato sugli inserimenti e sulle eliminazioni.
  3. Dopo l'esecuzione del blocco, esegue il doppio controllo dei conteggi calcolati sui conteggi effettivi quando richiede il proprio numero di dati "numeroOfItemsInSection" (questo è il numero 213). Se non corrisponde, si bloccherà.

In base alle vostre variabili 'insertedIndexes' e 'changedIndexes', sei pre-calcolo, che le cose devono presentarsi in base alla risposta da server, e quindi eseguire il batch. Tuttavia, suppongo che il metodo "numberOfItemsInSection" restituisca sempre il conteggio "vero" degli elementi.

Quindi, se un download viene completato durante il passaggio 2, quando si esegue il controllo di integrità in "3", i numeri non si allineano più.

Soluzione più semplice: attendere fino a quando tutti i file sono stati scaricati, quindi eseguire un singolo 'batchUpdates'. Probabilmente non è la migliore esperienza utente, ma evita questo problema.

Soluzione più complessa: eseguire i batch in base alle esigenze e tenere traccia di quali elementi sono già visualizzati/si stanno attualmente animando separatamente dal numero totale di elementi. Qualcosa di simile:

BOOL _performingAnimation; 
NSInteger _finalItemCount; 

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 
    return _finalItemCount; 
} 

- (void)somethingDidFinishDownloading { 
    if (_performingAnimation) { 
     return; 
    } 
    // Calculate changes. 
    dispatch_async(dispatch_get_main_queue(), 
      ^{ 
       _performingAnimation = YES; 
       [self.collectionView performBatchUpdates:^{ 

       if (removedIndexes && [removedIndexes count] > 0) { 
        [self.collectionView deleteItemsAtIndexPaths:removedIndexes]; 
       } 

       if (changedIndexes && [changedIndexes count] > 0) { 
        [self.collectionView reloadItemsAtIndexPaths:changedIndexes]; 
       } 

       if (insertedIndexes && [insertedIndexes count] > 0) { 
        [self.collectionView insertItemsAtIndexPaths:insertedIndexes]; 
       } 

       _finalItemCount += (insertedIndexes.count - removedIndexes.count); 
       } completion:^{ 
       _performingAnimation = NO; 
       }]; 
      }); 
} 

L'unica cosa da risolvere, dopo che sarebbe quello di assicurarsi di eseguire un controllo finale per gli oggetti rimanenti se l'ultimo elemento per scaricare finito durante un'animazione (forse avere un metodo 'performFinalAnimationIfNeeded' che si eseguire nel blocco di completamento)

+0

sto affrontando lo stesso problema. Proverò la tua soluzione. – vmeyer