2015-12-10 8 views
13

Sto indagando un bug schiantarsi con un UICollectionView tramite Crashlytics che richiede generalmente questa forma:È possibile utilizzare sia reloadItemsAtIndexPaths che reloadData se il conteggio dei dati può variare?

irreversibile: NSInternalInconsistencyException valido aggiornamento: numero non valido di voci nella sezione 0. Il numero di elementi contenuti in uno esistente la sezione successiva all'aggiornamento (25) deve essere uguale al numero di elementi contenuti in tale sezione prima dell'aggiornamento (27), più o meno il numero di elementi inseriti o cancellati da quella sezione (1 inserito, 1 cancellato) e più o meno il numero di elementi spostati in o fuori da quella sezione (0 spostato in, 0 spostato fuori).

Credo che questo sia causato perché ho un CollectionView che si aggiorna periodicamente con i dati da un server, e che i dati dal server può avere più o meno elementi che sono contenuti nel client UICollectionViewDataSource.

Quando ricevo nuovi dati dal server, chiamo reloadData sulla mia vista raccolta.

Tuttavia, è possibile che, a causa dell'interazione dell'utente con la mia vista raccolta prima che il download della rete fosse completato, avevo chiamato reloadItemsAtIndexPaths poco prima. reloadItemsAtIndexPaths non sembra terminare per almeno alcune centinaia di ms e molti cicli del processore. Quindi, questo arresto anomalo, quando il dataSource viene aggiornato nel mezzo di un reloadItemsAtIndexPaths.

Esiste una forma "immediata" di reloadItemsAtIndexPaths? O devo sempre chiamare reloadData dato il mio caso d'uso, che sembra aggiornare immediatamente tutto e lasciare il UICollectionView in buono stato alla fine.


Modifica

Ecco quello che hanno fatto, per consigliare dai TwoStraws:

// Prevent data source from batch updating while we work 
    self.dataSource.locked = YES; 

    [self.collectionView performBatchUpdates:^{ 
     [self.collectionView reloadItemsAtIndexPaths:@[indexPath]]; 
    } completion:^(BOOL finished) { 
     self.dataSource.locked = NO; 
    }]; 

Poi nella mia classe di origine di dati, dopo aver ricevuto i risultati dal server, ho sempre chiama assignResults:

- (void)assignResults:(NSMutableArray *)newResults { 
    if (!self.locked) { 
     self.results = newResults; 
     [self.delegate handleDataSourceUpdated:self]; 
    } else { 
     self.pendingResults = newResults; 
    } 
} 

- (void)setLocked:(BOOL)locked { 
    _locked = locked; 

    if (!locked && self.pendingResults) { 
     [self assignResults:self.pendingResults]; 
     self.pendingResults = nil; 
    } 
} 

Come utente puoi vedere, i risultati vengono assegnati solo se l'origine dati non è bloccata; altrimenti vengono assegnati quando la sorgente dati è sbloccata dallo UICollectionViewController. Nota che tutti questi metodi stanno accadendo sul thread principale, quindi non devo preoccuparmi della sincronizzazione della mia proprietà booleana, locked.

risposta

7

Le condizioni di gara sono sempre questioni complicate, come sono sicuro che tu sappia. Se ho capito bene, stai modificando l'origine dati della vista raccolta mentre sta ancora provando a ricaricarsi, il che significa che la soluzione è mantenere un archivio dati separato che viene copiato atomicamente dall'origine dati della vista dell'insieme.

Quindi:

  • vista Collection legge da fonte di dati A.
  • Network scrive per fonte di dati B.
  • In un punto specificato, copia B in A in un colpo solo.
  • Comunica alla vista di raccolta di ricaricare.

In questo modo la vista di raccolta non dovrebbe mai preoccuparsi delle condizioni di gara - si legge sempre da una serie fissa di dati per quanto è interessato.

+0

Non è chiaro per me che UICollectionView continuerà a "puntare" sull'origine dati A durante l'intera durata di un reloadItemsAtIndexPaths in questo approccio. Sappiamo se ciò che UICollectionView fa/dovrebbe funzionare? – esilver

+1

È possibile rilevare quando una vista raccolta inizia e termina gli aggiornamenti utilizzando il blocco di completamento sul metodo 'performBatchUpdates()' - AFAIK che deve essere attivato solo quando il caricamento è definitivamente terminato. Se si dispone di una situazione molto complicata, potrebbe essere necessario utilizzare una proprietà booleana per contrassegnare "Sto per avviare gli aggiornamenti" e "Ho sicuramente finito, è sicuro da copiare". – TwoStraws

+0

Grazie @twostraws: questo è quello che ho fatto come puoi vedere nelle mie modifiche sopra. – esilver

Problemi correlati