热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

CRASH尝试删除并重新加载相同的索引路径-CRASHattempttodeleteandreloadthesameindexpath

CollectionViewController.mline439__50-[CollectionViewControllerphotoLibraryDidChange:]_block_

CollectionViewController.m line 439 __50-[CollectionViewController photoLibraryDidChange:]_block_invoke

CollectionViewController.m第439行__50- [CollectionViewController photoLibraryDidChange:] _ block_invoke

Fatal Exception: NSInternalInconsistencyException attempt to delete and reload the same index path ( {length = 2, path = 0 - 26007})

致命异常:NSInternalInconsistencyException尝试删除并重新加载相同的索引路径({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];
        }
    });
}

source: https://developer.apple.com/devcenter/download.action?path=/wwdc_2014/wwdc_2014_sample_code/exampleappusingphotosframework.zip

来源:https://developer.apple.com/devcenter/download.action?path = / wddc_2014 / wwwdc_2014_sample_code / examplesappusingphotosframework.zip

I can't replicate the issue. What could be the problem? Thanks a lot!

我不能复制这个问题。可能是什么问题呢?非常感谢!

5 个解决方案

#1


17  

I was able to reproduce this today. To do this you need to:

我今天能够重现这一点。为此,您需要:

  1. Open your app that is listening for changes
  2. 打开正在侦听更改的应用
  3. Open the photos app, save a set of photos to your photo library from an iCloud shared album
  4. 打开照片应用,将一组照片从iCloud共享相册保存到照片库
  5. Go to the photos app, delete some of those photos
  6. 转到照片应用,删除其中一些照片
  7. Go again to the iCloud shared album and save again the some of the photos you deleted. You'll see this condition happen.
  8. 再次转到iCloud共享相册并再次保存您删除的部分照片。你会看到这种情况发生。

I found an updated code that seems to work better to handle the updating behavior here: https://developer.apple.com/library/ios/documentation/Photos/Reference/PHPhotoLibraryChangeObserver_Protocol/

我发现一个更新的代码似乎更好地处理更新行为:https://developer.apple.com/library/ios/documentation/Photos/Reference/PHPhotoLibraryChangeObserver_Protocol/

But it still doesn't handle this situation nor when the indexes to be deleted are bigger (i.e. Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete item 9 from section 0 which only contains 9 items before the update'). I created this updated version of this code that deals with this better and hasn't crashed for me anymore so far.

但它仍然没有处理这种情况,也没有当要删除的索引更大时(即由于未捕获的异常'NSInternalInconsistencyException'终止应用程序,原因:'尝试从更新前仅包含9个项目的第0部分删除第9项' )。我创建了这个代码的更新版本,可以更好地处理这个问题,并且到目前为止还没有为我崩溃。

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 / iOS 10 version:

Swift 3 / 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)
    }
}

#2


9  

I just moved the reloadItemsAtIndexPaths after the batch updates are completed to fix the crash of deleting and reloading at the same time.

我刚刚完成批量更新后移动了reloadItemsAtIndexPaths来修复同时删除和重新加载的崩溃。

From docs of changedIndexes of PHFetchResultChangeDetails:

来自PHFetchResultChangeDetails的changedIndexes的文档:

These indexes are relative to the original fetch result (the fetchResultBeforeChanges property) after you’ve applied the changes described by the removedIndexes and insertedIndexes properties; when updating your app’s interface, apply changes after removals and insertions and before moves.

在应用removedIndexes和insertedIndexes属性描述的更改后,这些索引相对于原始获取结果(fetchResultBeforeChanges属性);更新应用程序界面时,在删除和插入之后以及移动之前应用更改。

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

#3


7  

I implemented the code in batkryu's answer in Objective-C.

我在Objective-C中用batkryu的答案实现了代码。

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

#4


0  

This is an improvement to @batkru's answer, which eliminates the need for the variable shouldReload:

这是@ batkru的答案的改进,它消除了变量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()
            }
        }
    })
}

#5


0  

So I did well with @FernandoEscher's translation of @batkryu's solution, except in the situation where an iCloud Photo Library with tons of changes was recently re-conneted. In this situation the collection becomes totally un-responsive and can crash. The core problem is that photoLibraryDidChange will get called again before the performBatchUpdates completion fires. The call to performBatchUpdates before a performBatchUpdates finishes seems to kill performance. I suspect that the crash happens because assetsFetchResults gets modified while the animation is running for its previous value.

所以我在@ FernandoEscher的@ batkryu解决方案的翻译中做得很好,除了最近重新连接了大量变化的iCloud Photo Library之外。在这种情况下,集合变得完全无响应并且可能崩溃。核心问题是在performBatchUpdates完成触发之前将再次调用photoLibraryDidChange。在performBatchUpdates完成之前调用performBatchUpdates似乎会破坏性能。我怀疑崩溃的发生是因为在动画运行前一个值时,assetsFetchResults被修改了。

Sooooo, here's what I did:

Sooooo,这就是我做的:

elsewhere in the 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;
}

推荐阅读
author-avatar
黑m泽猫咪2009
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有