iOS8出現(xiàn)了Photo Kit,相信很多同學(xué)早已開始使用.隨著iOS11的到來,對于大部分App來說,iOS7應(yīng)該漸漸的退出歷史舞臺.如果想要重構(gòu)代碼,相信相冊是一個優(yōu)先級較高的模塊.
如何使用Photo Kit,有太多的資料,這里就不在贅述.這里提供一些tips,記錄下一些需要注意的地方.
API版本
注意API的版本.雖然Photo Kit是iOS8的產(chǎn)物,但是仍然由部分API是出現(xiàn)在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);
在回調(diào)中,返回一個PHContentEditingInput
,我們可以根據(jù)其fullSizeImageURL
屬性創(chuàng)建一個CIImage
,CIImage
的properties
即為exif信息.
但是這條API在iOS10和10以前是有差異的.根據(jù)文檔描述:
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以及以后會出現(xiàn)死鎖的現(xiàn)象.至于原因,也就是API的描述了.iOS10會主動切換到主線程.那為毛切換到主線程就會死鎖?這可以去詳細(xì)了解下GCD的原理啦!
所以..還是選擇方案1吧..
變更處理
變更處理算是相冊相關(guān)處理比價復(fù)雜的一個地方.
Photo Kit處理的看似很復(fù)雜,有很多個很奇怪的東西.例如:PHChange
,PHFetchResultChangeDetails
等.
但是仔細(xì)看看,實際上是非常容易理解的.
需要注意的是文檔的一些說明:
when updating your app’s interface, apply removals before insertions, changes,
and moves
這里提醒了處理順序.
然后變更可能會多次調(diào)用,即使你僅僅刪除了一張照片或者增加一張照片.多次調(diào)用的原因是changed
變更這個東東.可能系統(tǒng)會認(rèn)為你更新了某些照片.
所以合理的刷新策略是關(guān)鍵.如果收到了變更通知就刷新相關(guān)列表,可能會造成性能的浪費.
最后一個是,一般來講,照片的順序是依據(jù)創(chuàng)建時間或者修改時間.所以通常在處理insert
變更的時候,無論修改時間還是創(chuàng)建時間,都是最新的.那么可能直接就插入到照片數(shù)組的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
,如果直接轉(zhuǎn)換成UIImage
,那么相關(guān)的exif信息將會丟失.
所以需要做的是,將這個data寫入到文件.然后使用以下方法創(chuàng)建request,最終保存.
PHAssetChangeRequest *request = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:url];
照片
Photo Kit中取照片需要傳入一個size.并沒有ALAsset
中的thumb
等內(nèi)置尺寸.不過可以簡單的實驗出對應(yīng)的size.
而這里的size是絕對size(像素).例如:size傳入(100,100),那么無論在何種屏幕上,都是100*100的尺寸.有可能會在plus這種3x的屏幕上造成模糊.
所以更合理的做法是size乘以一個scale:
[UIScreen mainScreen].scale
緩存
官方提供了一個PHCachingImageManager
用于緩存.方法很簡單,只有一個start,兩個stop方法.
蘋果也提供了一個在scrollview中使用緩存的算法.核心思路是:
根據(jù)當(dāng)前位置,計算出一個更大的區(qū)域.將更大的區(qū)域中的cell/collectionView cell的index path計算出來.然后緩存這個更大的區(qū)域中的數(shù)據(jù).當(dāng)然,也會計算區(qū)域的差值,用于取消緩存.
類似于一個預(yù)加載+緩存的思路.
不過對于快速滑動的列表,預(yù)加載對于加載速度的提升起不了太大的作用.只有緩存是有用的.如果想要提升加載速度(非緩存).適當(dāng)?shù)臏p少獲取圖片的size是一個效果提升比較明顯的途徑.甚至可以針對中低端機型進行處理.合理的策略,能夠做到更好的用戶體驗.