ios - guia - CRASH intenta eliminar y volver a cargar la misma ruta de índice
qgis manual (5)
Esta es una mejora a la respuesta de @batkru, que elimina la necesidad de la variable shouldReload
:
func photoLibraryDidChange(changeInstance: PHChange) {
dispatch_async(dispatch_get_main_queue(), {
let changeDetails = changeInstance.changeDetailsForFetchResult(self.assetsFetchResult)
if let details = changeDetails {
self.assetsFetchResult = details.fetchResultAfterChanges
if details.hasIncrementalChanges {
var removedIndexes: [NSIndexPath]?
var insertedIndexes: [NSIndexPath]?
var changedIndexes: [NSIndexPath]?
if let removed = details.removedIndexes {
removedIndexes = createIndexPathsFromIndices(removed)
}
if let inserted = details.insertedIndexes {
insertedIndexes = createIndexPathsFromIndices(inserted)
}
if let changed = details.changedIndexes {
changedIndexes = createIndexPathsFromIndices(changed)
}
if removedIndexes != nil && changedIndexes != nil {
for removedIndex in removedIndexes! {
let indexOfAppearanceOfRemovedIndexInChangedIndexes = find(changedIndexes!, removedIndex)
if let index = indexOfAppearanceOfRemovedIndexInChangedIndexes {
changedIndexes!.removeAtIndex(index)
}
}
}
self.collectionView?.performBatchUpdates({
if let removed = removedIndexes {
self.collectionView?.deleteItemsAtIndexPaths(removed)
}
if let inserted = insertedIndexes {
self.collectionView?.insertItemsAtIndexPaths(inserted)
}
if let changed = changedIndexes {
self.collectionView?.reloadItemsAtIndexPaths(changed)
}
if details.hasMoves {
changeDetails!.enumerateMovesWithBlock({ fromIndex, toIndex in
self.collectionView?.moveItemAtIndexPath(NSIndexPath(forItem: fromIndex, inSection: 0), toIndexPath: NSIndexPath(forItem: toIndex, inSection: 0))
})
}
}, completion: nil)
} else {
self.collectionView?.reloadData()
}
}
})
}
CollectionViewController.m línea 439 __50- [CollectionViewController photoLibraryDidChange:] _ block_invoke
Fatal Exception: NSInternalInconsistencyException intenta eliminar y volver a cargar la misma ruta de índice ({length = 2, path = 0 - 26007})
- (void)photoLibraryDidChange:(PHChange *)changeInstance
{
// Call might come on any background queue. Re-dispatch to the main queue to handle it.
dispatch_async(dispatch_get_main_queue(), ^{
// check if there are changes to the assets (insertions, deletions, updates)
PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
if (collectionChanges) {
// get the new fetch result
self.assetsFetchResults = [collectionChanges fetchResultAfterChanges];
UICollectionView *collectionView = self.collectionView;
if (![collectionChanges hasIncrementalChanges] || [collectionChanges hasMoves]) {
// we need to reload all if the incremental diffs are not available
[collectionView reloadData];
} else {
// if we have incremental diffs, tell the collection view to animate insertions and deletions
[collectionView performBatchUpdates:^{
NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
if ([removedIndexes count]) {
[collectionView deleteItemsAtIndexPaths:[removedIndexes aapl_indexPathsFromIndexesWithSection:0]];
}
NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
if ([insertedIndexes count]) {
[collectionView insertItemsAtIndexPaths:[insertedIndexes aapl_indexPathsFromIndexesWithSection:0]];
}
NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
if ([changedIndexes count]) {
[collectionView reloadItemsAtIndexPaths:[changedIndexes aapl_indexPathsFromIndexesWithSection:0]];
}
} completion:NULL];
}
[self resetCachedAssets];
}
});
}
No puedo replicar el problema. ¿Cual podría ser el problema? ¡Muchas gracias!
Así que lo hice bien con la traducción de @WasEsEsEstra de la solución de @ batkryu, excepto en la situación en la que recientemente se volvió a conectar una fototeca de iCloud con toneladas de cambios. En esta situación, la colección deja de responder totalmente y puede colapsar. El problema central es que photoLibraryDidChange se volverá a llamar antes de que se complete la ejecución de performBatchUpdates. La llamada a performBatchUpdates antes de finalizar performBatchUpdates parece matar el rendimiento. Sospecho que el bloqueo ocurre porque assetsFetchResults se modifica mientras la animación se ejecuta por su valor anterior.
Sooooo, esto es lo que hice:
en otro lugar en el init ...
self.phPhotoLibChageMutex = dispatch_semaphore_create(1);
_
- (void)photoLibraryDidChange:(PHChange *)changeInstance {
dispatch_semaphore_wait(self.phPhotoLibChageMutex, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_main_queue(), ^{
PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
if (collectionChanges) {
self.assetsFetchResults = [collectionChanges fetchResultAfterChanges];
UICollectionView *collectionView = self.collectionView;
NSArray *removedPaths;
NSArray *insertedPaths;
NSArray *changedPaths;
if ([collectionChanges hasIncrementalChanges]) {
NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
removedPaths = [self indexPathsFromIndexSet:removedIndexes withSection:0];
NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
insertedPaths = [self indexPathsFromIndexSet:insertedIndexes withSection:0];
NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
changedPaths = [self indexPathsFromIndexSet:changedIndexes withSection:0];
BOOL shouldReload = NO;
if (changedPaths != nil && removedPaths != nil) {
for (NSIndexPath *changedPath in changedPaths) {
if ([removedPaths containsObject:changedPath]) {
shouldReload = YES;
break;
}
}
}
if (removedPaths.lastObject && ((NSIndexPath *)removedPaths.lastObject).item >= self.assetsFetchResults.count) {
shouldReload = YES;
}
if (shouldReload) {
[collectionView reloadData];
[self fixupSelection];
dispatch_semaphore_signal(self.phPhotoLibChageMutex);
} else {
[collectionView performBatchUpdates:^{
if (removedPaths) {
[collectionView deleteItemsAtIndexPaths:removedPaths];
}
if (insertedPaths) {
[collectionView insertItemsAtIndexPaths:insertedPaths];
}
if (changedPaths) {
[collectionView reloadItemsAtIndexPaths:changedPaths];
}
if ([collectionChanges hasMoves]) {
[collectionChanges enumerateMovesWithBlock:^(NSUInteger fromIndex, NSUInteger toIndex) {
NSIndexPath *fromIndexPath = [NSIndexPath indexPathForItem:fromIndex inSection:0];
NSIndexPath *toIndexPath = [NSIndexPath indexPathForItem:toIndex inSection:0];
[collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
}];
}
} completion:^(BOOL finished) {
[self fixupSelection];
dispatch_semaphore_signal(self.phPhotoLibChageMutex);
}];
}
[self resetCachedAssets];
} else {
[collectionView reloadData];
[self fixupSelection];
dispatch_semaphore_signal(self.phPhotoLibChageMutex);
}
}else{
dispatch_semaphore_signal(self.phPhotoLibChageMutex);
}
});
}
- (NSArray *)indexPathsFromIndexSet:(NSIndexSet *)indexSet withSection:(int)section {
if (indexSet == nil) {
return nil;
}
NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
[indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
[indexPaths addObject:[NSIndexPath indexPathForItem:idx inSection:section]];
}];
return indexPaths;
}
Acabo de mover el reloadItemsAtIndexPaths
después de que las actualizaciones del lote se completen para corregir el bloqueo al eliminar y volver a cargar al mismo tiempo.
De documentos de changedIndexes
de PHFetchResultChangeDetails
:
Estos índices son relativos al resultado de recuperación original (la propiedad fetchResultBeforeChanges) después de aplicar los cambios descritos por las propiedades removedIndexes e insertedIndexes; al actualizar la interfaz de su aplicación, aplique los cambios después de las eliminaciones e inserciones y antes de los movimientos.
PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
[collectionView performBatchUpdates:^{
NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
if ([removedIndexes count]) {
[collectionView deleteItemsAtIndexPaths:[self indexPathsFromIndexes:removedIndexes withSection:0]];
}
NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
if ([insertedIndexes count]) {
[collectionView insertItemsAtIndexPaths:[self indexPathsFromIndexes:insertedIndexes withSection:0]];
}
} completion:^(BOOL finished) {
if (finished) {
// Puting this after removes and inserts indexes fixes a crash of deleting and reloading at the same time.
// From docs: When updating your app’s interface, apply changes after removals and insertions and before moves.
NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
if ([changedIndexes count]) {
[collectionView reloadItemsAtIndexPaths:[self indexPathsFromIndexes:changedIndexes withSection:0]];
}
}
}
Pude reproducir esto hoy. Para hacer esto, necesitas:
- Abra su aplicación que está escuchando cambios
- Abra la aplicación de fotos, guarde un conjunto de fotos en su biblioteca de fotos desde un álbum compartido de iCloud
- Ve a la aplicación de fotos, borra algunas de esas fotos
- Vuelve al álbum compartido de iCloud y vuelve a guardar algunas de las fotos que borraste. Verás que esta condición sucede.
Encontré un código actualizado que parece funcionar mejor para manejar el comportamiento de actualización aquí: https://developer.apple.com/library/ios/documentation/Photos/Reference/PHPhotoLibraryChangeObserver_Protocol/
Pero todavía no maneja esta situación ni cuando los índices a eliminar son más grandes (es decir, aplicación de terminación debido a excepción no detectada ''NSInternalInconsistencyException'', razón: ''intento de eliminar el elemento 9 de la sección 0 que solo contiene 9 elementos antes de la actualización'' ) Creé esta versión actualizada de este código que trata mejor esto y hasta ahora no se ha bloqueado.
func photoLibraryDidChange(changeInfo: PHChange!) {
// Photos may call this method on a background queue;
// switch to the main queue to update the UI.
dispatch_async(dispatch_get_main_queue()) {
// Check for changes to the list of assets (insertions, deletions, moves, or updates).
if let collectionChanges = changeInfo.changeDetailsForFetchResult(self.assetsFetchResult) {
// Get the new fetch result for future change tracking.
self.assetsFetchResult = collectionChanges.fetchResultAfterChanges
if collectionChanges.hasIncrementalChanges {
// Get the changes as lists of index paths for updating the UI.
var removedPaths: [NSIndexPath]?
var insertedPaths: [NSIndexPath]?
var changedPaths: [NSIndexPath]?
if let removed = collectionChanges.removedIndexes {
removedPaths = self.indexPathsFromIndexSetWithSection(removed,section: 0)
}
if let inserted = collectionChanges.insertedIndexes {
insertedPaths = self.indexPathsFromIndexSetWithSection(inserted,section: 0)
}
if let changed = collectionChanges.changedIndexes {
changedPaths = self.indexPathsFromIndexSetWithSection(changed,section: 0)
}
var shouldReload = false
if changedPaths != nil && removedPaths != nil{
for changedPath in changedPaths!{
if contains(removedPaths!,changedPath){
shouldReload = true
break
}
}
}
if removedPaths?.last?.item >= self.assetsFetchResult.count{
shouldReload = true
}
if shouldReload{
self.collectionView.reloadData()
}else{
// Tell the collection view to animate insertions/deletions/moves
// and to refresh any cells that have changed content.
self.collectionView.performBatchUpdates(
{
if let theRemovedPaths = removedPaths {
self.collectionView.deleteItemsAtIndexPaths(theRemovedPaths)
}
if let theInsertedPaths = insertedPaths {
self.collectionView.insertItemsAtIndexPaths(theInsertedPaths)
}
if let theChangedPaths = changedPaths{
self.collectionView.reloadItemsAtIndexPaths(theChangedPaths)
}
if (collectionChanges.hasMoves) {
collectionChanges.enumerateMovesWithBlock() { fromIndex, toIndex in
let fromIndexPath = NSIndexPath(forItem: fromIndex, inSection: 0)
let toIndexPath = NSIndexPath(forItem: toIndex, inSection: 0)
self.collectionView.moveItemAtIndexPath(fromIndexPath, toIndexPath: toIndexPath)
}
}
}, completion: nil)
}
} else {
// Detailed change information is not available;
// repopulate the UI from the current fetch result.
self.collectionView.reloadData()
}
}
}
}
func indexPathsFromIndexSetWithSection(indexSet:NSIndexSet?,section:Int) -> [NSIndexPath]?{
if indexSet == nil{
return nil
}
var indexPaths:[NSIndexPath] = []
indexSet?.enumerateIndexesUsingBlock { (index, Bool) -> Void in
indexPaths.append(NSIndexPath(forItem: index, inSection: section))
}
return indexPaths
}
Swift 3 / versión iOS 10:
func photoLibraryDidChange(_ changeInstance: PHChange) {
guard let collectionView = self.collectionView else {
return
}
// Photos may call this method on a background queue;
// switch to the main queue to update the UI.
DispatchQueue.main.async {
guard let fetchResults = self.fetchResults else {
collectionView.reloadData()
return
}
// Check for changes to the list of assets (insertions, deletions, moves, or updates).
if let collectionChanges = changeInstance.changeDetails(for: fetchResults) {
// Get the new fetch result for future change tracking.
self.fetchResults = collectionChanges.fetchResultAfterChanges
if collectionChanges.hasIncrementalChanges {
// Get the changes as lists of index paths for updating the UI.
var removedPaths: [IndexPath]?
var insertedPaths: [IndexPath]?
var changedPaths: [IndexPath]?
if let removed = collectionChanges.removedIndexes {
removedPaths = self.indexPaths(from: removed, section: 0)
}
if let inserted = collectionChanges.insertedIndexes {
insertedPaths = self.indexPaths(from:inserted, section: 0)
}
if let changed = collectionChanges.changedIndexes {
changedPaths = self.indexPaths(from: changed, section: 0)
}
var shouldReload = false
if let removedPaths = removedPaths, let changedPaths = changedPaths {
for changedPath in changedPaths {
if removedPaths.contains(changedPath) {
shouldReload = true
break
}
}
}
if let item = removedPaths?.last?.item {
if item >= fetchResults.count {
shouldReload = true
}
}
if shouldReload {
collectionView.reloadData()
} else {
// Tell the collection view to animate insertions/deletions/moves
// and to refresh any cells that have changed content.
collectionView.performBatchUpdates({
if let theRemovedPaths = removedPaths {
collectionView.deleteItems(at: theRemovedPaths)
}
if let theInsertedPaths = insertedPaths {
collectionView.insertItems(at: theInsertedPaths)
}
if let theChangedPaths = changedPaths {
collectionView.reloadItems(at: theChangedPaths)
}
collectionChanges.enumerateMoves { fromIndex, toIndex in
collectionView.moveItem(at: IndexPath(item: fromIndex, section: 0),
to: IndexPath(item: toIndex, section: 0))
}
})
}
} else {
// Detailed change information is not available;
// repopulate the UI from the current fetch result.
collectionView.reloadData()
}
}
}
}
func indexPaths(from indexSet: IndexSet?, section: Int) -> [IndexPath]? {
guard let set = indexSet else {
return nil
}
return set.map { (index) -> IndexPath in
return IndexPath(item: index, section: section)
}
}
Implementé el código en la respuesta de batkryu en Objective-C.
- (void)photoLibraryDidChange:(PHChange *)changeInstance {
dispatch_async(dispatch_get_main_queue(), ^{
PHFetchResultChangeDetails *collectionChanges = [changeInstance changeDetailsForFetchResult:self.assetsFetchResults];
if (collectionChanges) {
self.assetsFetchResults = [collectionChanges fetchResultAfterChanges];
UICollectionView *collectionView = self.collectionView;
NSArray *removedPaths;
NSArray *insertedPaths;
NSArray *changedPaths;
if ([collectionChanges hasIncrementalChanges]) {
NSIndexSet *removedIndexes = [collectionChanges removedIndexes];
removedPaths = [self indexPathsFromIndexSet:removedIndexes withSection:0];
NSIndexSet *insertedIndexes = [collectionChanges insertedIndexes];
insertedPaths = [self indexPathsFromIndexSet:insertedIndexes withSection:0];
NSIndexSet *changedIndexes = [collectionChanges changedIndexes];
changedPaths = [self indexPathsFromIndexSet:changedIndexes withSection:0];
BOOL shouldReload = NO;
if (changedPaths != nil && removedPaths != nil) {
for (NSIndexPath *changedPath in changedPaths) {
if ([removedPaths containsObject:changedPath]) {
shouldReload = YES;
break;
}
}
}
if (removedPaths.lastObject && ((NSIndexPath *)removedPaths.lastObject).item >= self.assetsFetchResults.count) {
shouldReload = YES;
}
if (shouldReload) {
[collectionView reloadData];
} else {
[collectionView performBatchUpdates:^{
if (removedPaths) {
[collectionView deleteItemsAtIndexPaths:removedPaths];
}
if (insertedPaths) {
[collectionView insertItemsAtIndexPaths:insertedPaths];
}
if (changedPaths) {
[collectionView reloadItemsAtIndexPaths:changedPaths];
}
if ([collectionChanges hasMoves]) {
[collectionChanges enumerateMovesWithBlock:^(NSUInteger fromIndex, NSUInteger toIndex) {
NSIndexPath *fromIndexPath = [NSIndexPath indexPathForItem:fromIndex inSection:0];
NSIndexPath *toIndexPath = [NSIndexPath indexPathForItem:toIndex inSection:0];
[collectionView moveItemAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
}];
}
} completion:NULL];
}
[self resetCachedAssets];
} else {
[collectionView reloadData];
}
}
});
}
- (NSArray *)indexPathsFromIndexSet:(NSIndexSet *)indexSet withSection:(int)section {
if (indexSet == nil) {
return nil;
}
NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
[indexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
[indexPaths addObject:[NSIndexPath indexPathForItem:idx inSection:section]];
}];
return indexPaths;
}