再谈PhotoKit

作者: CocoaMan | 来源:发表于2018-07-26 11:18 被阅读8次

    PhotoKit作为iOS8新推出的照片库,相比较于之前的ALAssetsLibrary确实解决了不少问题,那么结合我最近的使用,也借鉴了"TZImagePickerController"、"CTAssetsPickerController"等流行的开源框架,以及官方的SwiftDemo,来讲讲几个难以察觉的点。

    内存问题

    自定义相册基本思路都是拿UICollectionView来展示各种列表,那么内存主要就存在于对照片缩略图的获取以及滑动卡顿的问题,我们来看看TZImagePickerController,

    __block UIImage *image;
            // 修复获取图片时出现的瞬间内存过高问题
            // 下面两行代码,来自hsjcom,他的github是:https://github.com/hsjcom 表示感谢
            PHImageRequestOptions *option = [[PHImageRequestOptions alloc] init];
            option.resizeMode = PHImageRequestOptionsResizeModeFast;
            int32_t imageRequestID = [[PHImageManager defaultManager] requestImageForAsset:asset targetSize:imageSize contentMode:PHImageContentModeAspectFill options:option resultHandler:^(UIImage *result, NSDictionary *info) {
                if (result) {
                    image = result;
                }
                BOOL downloadFinined = (![[info objectForKey:PHImageCancelledKey] boolValue] && ![info objectForKey:PHImageErrorKey]);
                if (downloadFinined && result) {
                    result = [self fixOrientation:result];
                    if (completion) completion(result,info,[[info objectForKey:PHImageResultIsDegradedKey] boolValue]);
                }
                // Download image from iCloud / 从iCloud下载图片
                if ([info objectForKey:PHImageResultIsInCloudKey] && !result && networkAccessAllowed) {
                    PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
                    options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
                        dispatch_async(dispatch_get_main_queue(), ^{
                            if (progressHandler) {
                                progressHandler(progress, error, stop, info);
                            }
                        });
                    };
                    options.networkAccessAllowed = YES;
                    options.resizeMode = PHImageRequestOptionsResizeModeFast;
                    [[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
                        UIImage *resultImage = [UIImage imageWithData:imageData scale:0.1];
                        resultImage = [self scaleImage:resultImage toSize:imageSize];
                        if (!resultImage) {
                            resultImage = image;
                        }
                        resultImage = [self fixOrientation:resultImage];
                        if (completion) completion(resultImage,info,NO);
                    }];
                }
            }];
    

    注释有提到修复了瞬间内存过高,但测试发现iPad mini上快速滑动依然会有内存警告最终导致Crash。(CTAssetsPickerController同样如此)这里PHImageRequestOptionsResizeModeFast设置,会有两次结果回调,一次模糊,一次清晰(如果有的话),一般来说没什么问题。有说可以通过requestImageDataForAsset解决,没错这种方式内存占用确实要小不少,但Data到Image这一步需要做不少处理吧,并没有之前的API那么方便。我的结论是将contentMode改为PHImageContentModeAspectFit,同时尽量利用PHCachingImageManager来做缓存,这样效果会好很多。

    iCloud问题

    目前的系统设置有几个选项,针对用户开启了iCloud照片库,并且选择了“优化iPhone/iPad存储空间”或者选择了“下载并保留原件”但原件还未加载出来,也就是说资源不在本地。PHImageRequestOptions或者PHVideoRequestOptions在开启了PHVideoRequestOptions,会试图从iCloud去下载资源,这时候耗时可能会很长,另外也可能载入不成功,这两点都必须有严格的过度处理。这两点在之前的两个开源库中都有体现。而在我们开发中,大部分可能都忽视了这一点。

    相册变更

    这个主要是系统相册的增删,或者iCloud的更新对自定义相册的影响。原本是参考官方Swif版本来进行处理,没想到坑就在这里,同时更新和删除会Crash,官方Demo也不例外。主要是在performBatchUpdates这个地方。造成了线上好些崩溃记录。目前的处理如下

        if (changes == nil) {
            return;
        }
        
        dispatch_async(dispatch_get_main_queue(), ^{
            
            self.allVideos = changes.fetchResultAfterChanges;
            
            UICollectionView *collectionView = self.collectionView;
            
            if (!changes.hasIncrementalChanges || changes.hasMoves)
            {
                [collectionView reloadData];
                [self fixupSelection];
                [self resetCachedAssets];
            }
            else
            {
                NSArray *removedPaths;
                NSArray *insertedPaths;
                NSArray *changedPaths;
                
                NSIndexSet *removedIndexes = changes.removedIndexes;
                removedPaths = [removedIndexes vk_assetGridIndexPathsFromIndexesWithSection:0];
                
                NSIndexSet *insertedIndexes = changes.insertedIndexes;
                insertedPaths = [insertedIndexes vk_assetGridIndexPathsFromIndexesWithSection:0];
                
                NSIndexSet *changedIndexes = changes.changedIndexes;
                changedPaths = [changedIndexes vk_assetGridIndexPathsFromIndexesWithSection: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.allVideos.count)
                {
                    shouldReload = YES;
                }
                
                if (shouldReload)
                {
                    [collectionView reloadData];
                    [self fixupSelection];
                }
                else
                {
                    [collectionView performBatchUpdates:^{
                        if (removedPaths.count)
                        {
                            [collectionView deleteItemsAtIndexPaths:[removedIndexes vk_assetGridIndexPathsFromIndexesWithSection:0]];
                        }
                        
                        if (insertedPaths.count)
                        {
                            [collectionView insertItemsAtIndexPaths:[insertedIndexes vk_assetGridIndexPathsFromIndexesWithSection:0]];
                        }
                        
                        if (changedPaths.count)
                        {
                            [collectionView reloadItemsAtIndexPaths:[changedIndexes vk_assetGridIndexPathsFromIndexesWithSection:0] ];
                        }
                    } completion:^(BOOL finished){
                        if (finished) {
                            [self resetCachedAssets];
                            [self fixupSelection];
                        }
                    }];
                }
            }
            
            [self.emptyView setHidden:self.allVideos.count > 0];
            
        });
    

    视频封面截取帧

    封面要求截取9张图片,之前有通过循环一次性截取9张图片,但这个操作有些耗时,看到系统有AVAssetImageGenerator,所以采用了这种方案。但这个API有一个问题,传入的Times数组是9个值,回调时并不一定给你回调9次,有可能更多,所以在回调时候最好做一些边界处理。

    以上就是这次使用遇到的一系列问题。

    相关文章

      网友评论

        本文标题:再谈PhotoKit

        本文链接:https://www.haomeiwen.com/subject/btavmftx.html