iOS中的圖片使用方式、記憶體對比和最佳實踐
預備知識
我們的對比主要關注記憶體的佔用情況。對比的格式是jpg和png這兩種最廣泛使用的格式,分別代表了有失真壓縮和無失真壓縮;關於它們的特點和介紹,可以參考郭耀源的這篇文章:移動端圖片格式調研。我們可以看到,在iOS裝置上它們的解碼消耗在一個量級,速度較快。
wwdc2018蘋果重點關注了圖片的記憶體佔用情況(因為此前大家的通用做法實際上是相對低效的),並且給了大家一些指導。這裡我們會用demo實驗的方式(並且通過工具來進行benchmark),為大家揭示一些事實和現象,供大家去理解和分析,從而去形成我們程式碼的最佳實踐。
Demo:DownSampleDemo
場景
首先,蘋果告訴我們,圖片在應用中主要的記憶體佔用(這通常發生在圖片要被載入並顯示時)和圖片本身的大小實際是無關的;重要的是圖片的尺寸。decode buffer的計算方式是width*height*N,這裡的N通常是4(最常見的ARGB888格式),N取決於你顯示所使用的格式。但很多時候,我們會直接把一個UIImage傳遞給UIImageView, 實際上該View的尺寸可能遠遠的小於UIImage本身。
使用圖片的三種方式:
方式A
image1.image = UIImage(named: "000.jpg")
複製程式碼
這是我們最通常使用圖片的方式,可能有人會想到imagewithcontentsoffile,實際上它和上面的方法只有一個不同之處: 正常情況下imageNamed會快取這個圖片在整個app存續期間,這樣就無需重複的decode;而imagewithcontentsoffile則沒有這個快取,圖片不使用了就會釋放記憶體。但這對我們的case並無幫助,因為我們是在使用這些圖片,並且觀察它們的記憶體佔用.
方式B
+ (UIImage*)OriginImage:(UIImage *)image scaleToSize:(CGSize)size
{
// 建立一個bitmap的context
// 並把它設定成為當前正在使用的context
UIGraphicsBeginImageContext(size);
// 繪製改變大小的圖片
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
// 從當前context中建立一個改變大小後的圖片
UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
// 使當前的context出堆疊
UIGraphicsEndImageContext();
// 返回新的改變大小後的圖片
return scaledImage;
}
複製程式碼
這是一種被廣泛使用的縮放圖片的辦法。
方式C
func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage
{
let sourceOpt = [kCGImageSourceShouldCache : false] as CFDictionary
// 其他場景可以用createwithdata (data並未decode,所佔記憶體沒那麼大),
let source = CGImageSourceCreateWithURL(imageURL as CFURL, sourceOpt)!
let maxDimension = max(pointSize.width, pointSize.height) * scale
let downsampleOpt = [kCGImageSourceCreateThumbnailFromImageAlways : true,
kCGImageSourceShouldCacheImmediately : true ,
kCGImageSourceCreateThumbnailWithTransform : true,
kCGImageSourceThumbnailMaxPixelSize : maxDimension] as CFDictionary
let downsampleImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOpt)!
return UIImage(cgImage: downsampleImage)
}
複製程式碼
這是蘋果介紹給我們的新方法。
關於測試
測試裝置: 1.iphone8 11.4 2.iphone6 12beta2
測試圖片格式: png/jpg
測試的圖片使用方式:3種
我們將2560*1440大小的圖片放到282*138的view中,對於縮放我們使用2x的格式來顯示以保證其效果。
實驗資料的細節在DownSampleDemo的ViewController.swift的註釋中;直接告訴大家結論:
- xcode皮膚上所顯示的記憶體佔用並不可靠,在很多case下應用佔用的記憶體要比其上顯示的多的多(特別是對於iOS11.4的裝置)。但可以用來粗略的檢視記憶體佔用,如果這裡超了很多,那肯定是超了
- 可以使用instruments的allocation和memory debugging的memgraph+命令列分析這兩種方式來觀測記憶體的佔用
- 第一種方式下jpg載入後的記憶體分為imageIO和IOkit兩部分,png載入只有imageIO部分,jpg記憶體佔用比png要少不少
- 第二種方式的資料在xcode皮膚上失真,通過另外兩種方式可以看到它非常不靠譜。記憶體消耗甚至可能超過第一種。
- 第三種方式的記憶體佔用非常完美,嚴格的遵守了公式計算出來的大小(跟你downsample後的尺寸有關),記憶體佔用在CG raster data中。
- memgraph命令: vmmap --summary xxx.memgraph
- allocation我們只需要看VM allocation欄目下的dirty size和swapped size,這裡需要手動的開啟snapshot才能看到該欄目的資料,並且需要適當的重複多次執行才能保證結果的正確性
VMTracker執行截圖:
memgraph命令結果截圖: