再谈PhotoKit

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)