iOS8出现了Photo Kit,相信很多同学早已开始使用.随着iOS11的到来,对于大部分App来说,iOS7应该渐渐的退出历史舞台.如果想要重构代码,相信相册是一个优先级较高的模块.
如何使用Photo Kit,有太多的资料,这里就不在赘述.这里提供一些tips,记录下一些需要注意的地方.
API版本
注意API的版本.虽然Photo Kit是iOS8的产物,但是仍然由部分API是出现在iOS9上.例如PHFetchOptions
的fetchLimit
.
API特性差异
API也在不同的iOS版本中迭代.其中造成了特性的差异.例如在获取图片的exif信息时,我们通常使用一条API:
- (PHContentEditingInputRequestID)requestContentEditingInputWithOptions:(nullable PHContentEditingInputRequestOptions *)options completionHandler:(void (^)(PHContentEditingInput *__nullable contentEditingInput, NSDictionary *info))completionHandler PHOTOS_AVAILABLE_IOS_TVOS(8_0, 10_0);
在回调中,返回一个PHContentEditingInput
,我们可以根据其fullSizeImageURL
属性创建一个CIImage
,CIImage
的properties
即为exif信息.
但是这条API在iOS10和10以前是有差异的.根据文档描述:
In iOS 10.0, tvOS 10.0, and later, Photos always calls this block on the main
queue. In earlier releases, Photos calls this block on an arbitrary serial queue
—if your block needs to update the UI, dispatch that work to the main queue.
所以会引申出一个问题:
在兼容iOS7的时代,我们通常使用ALAsset
的metaData
作为exif信息.这是同步的.但如果切换成Photo kit,取exif信息则是异步的.并且该API并没有同步选项.那么此时有2种选择:
- 修改代码逻辑,切换成异步.
- 修改获取exif方法为同步.
如果选择方案1,那么OK.没有任何影响.如果采用方案2,那么通常采用dispatch_semaphore_t
来进行处理.
事实上,如此处理的话,在iOS10以前都是OK的.但是iOS10以及以后会出现死锁的现象.至于原因,也就是API的描述了.iOS10会主动切换到主线程.那为毛切换到主线程就会死锁?这可以去详细了解下GCD的原理啦!
所以..还是选择方案1吧..
变更处理
变更处理算是相册相关处理比价复杂的一个地方.
Photo Kit处理的看似很复杂,有很多个很奇怪的东西.例如:PHChange
,PHFetchResultChangeDetails
等.
但是仔细看看,实际上是非常容易理解的.
需要注意的是文档的一些说明:
when updating your app’s interface, apply removals before insertions, changes,
and moves
这里提醒了处理顺序.
然后变更可能会多次调用,即使你仅仅删除了一张照片或者增加一张照片.多次调用的原因是changed
变更这个东东.可能系统会认为你更新了某些照片.
所以合理的刷新策略是关键.如果收到了变更通知就刷新相关列表,可能会造成性能的浪费.
最后一个是,一般来讲,照片的顺序是依据创建时间或者修改时间.所以通常在处理insert
变更的时候,无论修改时间还是创建时间,都是最新的.那么可能直接就插入到照片数组的0的index了.
实际上还要考虑到用户可能修改了时间,照片中存在着未来时间这样一种情况.
exif
在ALLibrary
中,提供了一条api:
- (void)writeImageToSavedPhotosAlbum:(CGImageRef)imageRef metadata:(NSDictionary *)metadata completionBlock:(ALAssetsLibraryWriteImageCompletionBlock)completionBlock NS_DEPRECATED_IOS(4_1, 9_0, "Use creationRequestForAssetFromImage: on PHAssetChangeRequest from the Photos framework to create a new asset instead");
这条api可以连同图片和exif信息一起写入.
而Photo Kit并没有提供类似的方法.那么需要自己处理.
可以这样将exif信息写入image:
- (NSData *)appendExif:(NSDictionary *)exif toImage:(UIImage *)image {
NSData *data = UIImageJPEGRepresentation(image, 1);
CFDataRef cfData = CFBridgingRetain(data);
CGImageSourceRef imageSource = CGImageSourceCreateWithData(cfData, nil);
CFBridgingRelease(cfData);
NSDictionary *defaultAttributes = (__bridge NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imageSource, 0, NULL);
NSMutableDictionary *attributes = defaultAttributes.mutableCopy;
[attributes addEntriesFromDictionary:exif];
NSMutableData *output = [NSMutableData new];
CGImageDestinationRef imageDestination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)output, kUTTypeJPEG, 1, NULL);
CGImageDestinationAddImageFromSource(imageDestination, imageSource, 0, (__bridge CFDictionaryRef)attributes);
CGImageDestinationFinalize(imageDestination);
CFRelease(imageDestination);
return output;
}
但是这返回的是NSData
,如果直接转换成UIImage
,那么相关的exif信息将会丢失.
所以需要做的是,将这个data写入到文件.然后使用以下方法创建request,最终保存.
PHAssetChangeRequest *request = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:url];
照片
Photo Kit中取照片需要传入一个size.并没有ALAsset
中的thumb
等内置尺寸.不过可以简单的实验出对应的size.
而这里的size是绝对size(像素).例如:size传入(100,100),那么无论在何种屏幕上,都是100*100的尺寸.有可能会在plus这种3x的屏幕上造成模糊.
所以更合理的做法是size乘以一个scale:
[UIScreen mainScreen].scale
缓存
官方提供了一个PHCachingImageManager
用于缓存.方法很简单,只有一个start,两个stop方法.
苹果也提供了一个在scrollview中使用缓存的算法.核心思路是:
根据当前位置,计算出一个更大的区域.将更大的区域中的cell/collectionView cell的index path计算出来.然后缓存这个更大的区域中的数据.当然,也会计算区域的差值,用于取消缓存.
类似于一个预加载+缓存的思路.
不过对于快速滑动的列表,预加载对于加载速度的提升起不了太大的作用.只有缓存是有用的.如果想要提升加载速度(非缓存).适当的减少获取图片的size是一个效果提升比较明显的途径.甚至可以针对中低端机型进行处理.合理的策略,能够做到更好的用户体验.