iOS中的圖片使用方式、記憶體對比和最佳實踐

阿塵_dustturtle發表於2018-06-23

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的註釋中;直接告訴大家結論:

  1. xcode皮膚上所顯示的記憶體佔用並不可靠,在很多case下應用佔用的記憶體要比其上顯示的多的多(特別是對於iOS11.4的裝置)。但可以用來粗略的檢視記憶體佔用,如果這裡超了很多,那肯定是超了
  2. 可以使用instruments的allocation和memory debugging的memgraph+命令列分析這兩種方式來觀測記憶體的佔用
  3. 第一種方式下jpg載入後的記憶體分為imageIO和IOkit兩部分,png載入只有imageIO部分,jpg記憶體佔用比png要少不少
  4. 第二種方式的資料在xcode皮膚上失真,通過另外兩種方式可以看到它非常不靠譜。記憶體消耗甚至可能超過第一種。
  5. 第三種方式的記憶體佔用非常完美,嚴格的遵守了公式計算出來的大小(跟你downsample後的尺寸有關),記憶體佔用在CG raster data中。
  6. memgraph命令: vmmap --summary xxx.memgraph
  7. allocation我們只需要看VM allocation欄目下的dirty size和swapped size,這裡需要手動的開啟snapshot才能看到該欄目的資料,並且需要適當的重複多次執行才能保證結果的正確性

VMTracker執行截圖:

iOS中的圖片使用方式、記憶體對比和最佳實踐
memgraph命令結果截圖:
iOS中的圖片使用方式、記憶體對比和最佳實踐

最後,來自蘋果的最佳實踐:強烈推薦大家使用downsample來處理大圖!Thank you!:]

相關文章