iOS 開發實踐:iOS照片API的那些坑
作者簡介:keyishen(沈珂軼)天天P圖 iOS 工程師
在和圖片打交道的那些日子裡,遇到過不少圖片相關的詭異問題。 在這裡不會具體對照片API做介紹,而只會對其中的一些坑做一些總結。
1.不要完全相信系統API
當我們的程序有crash,但是通常我們的crash上報系統會上報自己app的crashlog,例如以下crashlog:
------------------------------------------------------------------------------------------------
由於是很偶現的crash,反覆檢查代碼也沒有發現什麼疑點。還好在測試手機上後來發現了同一時間的另一個crashlog,如下:
------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------
原來在同一時間,系統負責AssetsLibrary功能的進程/System/Library/Frameworks/AssetsLibrary.framework/Support/assetsd crash了。從而導致了-[ALAsset valueForProperty:] 的調用始終卡在了那裡。
系統的圖片相關的操作主要是通過assetsd進程來實現的。
在對系統相冊做一系列複雜操作後,有時會把系統assetsd進程搞掛,如果這時再回到app內調用Photos相關的API,就會出現異常現象,比如卡住,或者crash。這時可以看看手機里同一時刻之前是否有系統asset進程的crash堆棧。
我們目前的crash上報系統,往往無法定位到這張情況的問題。對於這種需要查看關聯crash的情況就需要對系統的API也要合理地質疑。
2.不推薦自己寫選圖控制項
對於選圖沒有太高要求的app,建議使用系統的選圖控制項UIImagePickerController,這樣開發快捷便利,但是在我看來最大的優點在於這樣做未來的維護成本會很小,尤其是可以在未來幾乎第一時間享受到系統選圖控制項的新功能和新特性。
當然缺點是可調整的東西少,無法做個性化的定製,例如,不能控制UI,也很難做多圖選擇的擴展。
不過最不推薦的是在UIImagePickerController上面做UI的修改,這樣雖然能夠滿足一時需求,但是卻是為未來埋下了不少隱患,維護成本很高。
在iOS 10,如果用UIImagePickerController的話,還需要規避一個系統API會crash的坑。
3.支持的最低系統版本
蘋果官方推薦始終只支持最新的2個大系統,就今天而言(2018.7)理論上我們應該只用支持iOS 10和iOS 11。然而國內的大環境,使得我們通常還需要從iOS 7,或者iOS 8開始支持。
對於圖片類App來說,有一條很重要的分水嶺,那就是iOS 8.1。
iOS 8開啟了Photos.framework的新時代,而iOS 7及以下開發者只能使用AssetsLibrary的API。
然而可能是由於iOS 8推出匆忙,在iOS 8.0.x系統上,PHAsset的fetchAssetsWithMediaType: 和 fetchAssetsWithOptions:方法會返回iTunes同步的照片,以及iCloud照片流上的照片,所以如果你的照片被傳到照片流上去後,通過這兩個API返回的相冊列表裡會有兩份相同的照片。
好在在iOS 8.1上蘋果修改了這一API的行為,不再返回iTunes照片,以及照片流照片,使得Photos.framework從整個版本開始才真正意義上是可用的了。
對於小於iOS 8.1的系統都需要同時AssetsLibrary和Photos.framework兩套圖片API,對於非圖片重度的app來說工作量不小。所以,推薦直接從iOS 9開始支持,如果不行的話,推薦至少從iOS 8.1開始支持(當然更合理的是從8.4開始支持,這樣升級不到iOS 9的手機也有機會使用上)。
4.iCloud的坑
如果使用Photos.framework,那避不開的問題就是要支持iCloud上的照片。
為了推廣蘋果自己的iCloud的服務,並且拯救那些16GB手機的空間,蘋果會將照片上傳到iCloud,並且在本地只保存一份低清的照片。
4.1判斷是否在iCloud上
我們APP在選圖時需要判斷本地是否存有原圖。
Photos.framework提供了requestImageDataForAsset來獲取圖片的info。
Info的內容大致是這樣的:
Printing description of info:
{
PHImageResultDeliveredImageFormatKey = 9999;
PHImageResultIsDegradedKey = 0;
PHImageResultIsInCloudKey = 1;
PHImageResultIsPlaceholderKey = 0;
PHImageResultRequestIDKey = 54;
PHImageResultWantedImageFormatKey = 9999;
}
理論上,info中的PHImageResultIsInCloudKey欄位會告訴APP圖片是否在iCloud上。
然而實際測試下來,該系統API有一個坑。在以上option的設置下,即使剛剛成功下載了這張圖片,返回的info還是PHImageResultIsInCloudKey=1。
所以如果自己成功下載過圖片後,還需要自己另外記憶下載的狀態,在一定時間內,如果剛剛成功下載了圖片,就應當要無視PHImageResultIsInCloudKey欄位的狀態,因為該欄位的更新有滯後。
4.2判斷iCloud API的卡死
在iOS 10系統上,還有一個新的坑,那就是用requestImageDataForAsset這個API會有一定概率出現永遠不執行回調。
根據對在線用戶的性能監控,我們發現這甚至是造成我們app卡頓的最大原因。通過對場景的重現我們發現,當這個API出現不回調bug時,各大主流app也幾乎無一倖免,基本都卡在了那裡。
所以為了解決這個問題,在調用requestImageDataForAsset的時候切忌放在主線程同步地做,並且需要給它一個超時時間,不讓它無限制的執行。
5.正確獲取縮略圖
- (PHImageRequestID)requestImageForAsset:(PHAsset*)asset targetSize:(CGSize)targetSize contentMode:(PHImageContentMode)contentMode options:(nullablePHImageRequestOptions*)options resultHandler:(void(^)(UIImage*__nullableresult,NSDictionary*__nullableinfo))resultHandler;
通過以上API,我們可以獲取到各種尺寸的圖片。雖然靈活性比之前AssetsLibrary的獲取縮略圖API高很多,但是方便程度差了不少,更多的靈活性帶了的問題也不少。
這裡涉及到一個系統API的坑requestImageForAsset這個API在某些targetSize下返回為空。
例如,在測試設備iPadAir(iOS 9.3.1)上,
如果targetSize=CGSizeMake(x, x),
當contentMode=PHImageContentModeAspectFill時,
且1
resultHandler返回的result image為nil。
當contentMode=PHImageContentModeAspectFit時,
且121
resultHandler返回的result image為nil。
在調用時需要多試試各個系統以及機型的適配性,盡量避開這些取值範圍。
6.刪除圖片的API
在低於iOS 8的系統上,AssetsLibrary沒有明確地提供刪除圖片的介面。
但事實上很多圖片類APP通過修改圖片的介面起到刪除圖片的作用,即通過ALAsset的介面直接刪除圖片。
但是當base sdk到了iOS 10之後,我們發現之前能用的介面現在在iOS 8.1及以上系統,會出現成功回調不執行的問題。
解決方法也很簡單,就是直接使用Photos.framework提供的介面來刪除圖片:
附錄:
https://objccn.io/issue-21-4/
http://stackoverflow.com/questions/25883005/avoiding-duplicates-when-getting-pictures-with-phasset
文章後記:
天天P圖是由騰訊公司開發的業內領先的圖像處理,相機美拍的APP。歡迎掃碼或搜索關注我們的微信公眾號:「天天P圖攻城獅」,那上面將陸續公開分享我們的技術實踐,期待一起交流學習!
加入我們:
天天P圖技術團隊長期招聘:
(1) 圖像處理演算法工程師(2) Android / iOS 開發工程師
TAG:天天P圖攻城獅 |