成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

App的內存優化

移動開發 iOS
文章的前篇主要是對兩種不同的UIImage工廠方法的分析, 羅列出這些工廠方法的內存管理的優缺點。文章的后篇是本文要說明的重點, 如何結合兩種工廠方法的優點做更進一步的節約內存的管理。

[[183249]]

這篇文章是筆者在開發App過程中發現的一些內存問題, 然后學習了YYKit框架時候也發現了圖片的緩存處理 (YYKit 作者聯系了我, 說明了YYKit重寫imageNamed:的目的不是為了內存管理, 而是增加兼容性, 同時也是為了YYKit中的動畫服務). 以下內容是筆者在開發中做了一些實驗以及總結. 如有錯誤望即時提出, 筆者會***時間改正.

文章的前篇主要是對兩種不同的UIImage工廠方法的分析, 羅列出這些工廠方法的內存管理的優缺點.

文章的后篇是本文要說明的重點, 如何結合兩種工廠方法的優點做更進一步的節約內存的管理.

PS

本文所說的 Resource 是指使用imageWithContentsOfFile:創建圖片的圖片管理方式.

ImageAssets 是指使用imageNamed:創建圖片的圖片管理方式.

如果你對這兩個方法已經了如指掌, 可以直接看UIImage 與 YYImage 的內存問題和后面的內容

[TOC]

UIImage 的內存處理

在實際的蘋果App開發中, 將圖片文件導入到工程中無非使用兩種方式. 一種是 Resource (我也不知道應該稱呼什么,就這么叫吧),還有一種是 ImageAssets 形式存儲在一個圖片資源管理文件中. 這兩種方式都可以存儲任何形式的圖片文件, 但是都有各自的優缺點在內. 接下來我們就來談談這兩種圖片數據管理方式的優缺點.

Resource 與 “imageWithContentsOfFile:”

Resource 的使用方式

將文件直接拖入到工程目錄下, 并告訴Xcode打包項目時候把這些圖片文件打包進去. 這樣在應用的”.app”文件夾中就有這些圖片. 在項目中, 讀取這些圖片可以通過以下方式來獲取圖片文件并封裝成UIImge對象:

  1. NSString *path = [NSBundle.mainBundle pathForResource:@"image@2x" type:@"png"]; 
  2.  
  3. UIImage *image = [UIImage imageWithContentsOfFile:path];  

而底層的實現原理近似是:

  1. + (instancetype)imageWithContentsOfFile:(NSString *)fileName { 
  2.  
  3.     NSUInteger scale = 0; 
  4.  
  5.     { 
  6.  
  7.         scale = 2;//這一部分是取 fileName 中"@"符號后面那個數字, 如果不存在則為1, 這一部分的邏輯省略 
  8.  
  9.     } 
  10.  
  11.     return [[self alloc] initWithData:[NSData dataWithContentsOfFile:fileName scale:scale]; 
  12.  
  13.  

這種方式有一個局限性, 就是圖片文件必須在.ipa的根目錄下或者在沙盒中. 在.ipa的根目錄下創建圖片文件僅僅只有一種方式, 就是通過 Xcode 把圖片文件直接拖入工程中. 還有一種情況也會創建圖片文件, 就是當工程支持低版本的 iOS 系統時, 低版本的iOS系統并不支持 ImageAssets 打包文件的圖片讀取, 所以 Xcode 在編譯時候會自動地將 ImageAssets 中的圖片復制一份到根目錄中. 此時也可以使用這個方法創建圖片.

Resource 的特性

在 Resource 的圖片管理方式中, 所有的圖片創建都是通過讀取文件數據得到的, 讀取一次文件數據就會產生一次NSData以及產生一個UIImage, 當圖片創建好后銷毀對應的NSData, 當UIImage的引用計數器變為0的時候自動銷毀UIImage. 這樣的話就可以保證圖片不會長期地存在在內存中.

Resource 的常用情景

由于這種方法的特性, 所以 Resource 的方法一般用在圖片數據很大, 圖片一般不需要多次使用的情況. 比如說引導頁背景(圖片全屏, 有時候運行APP會顯示, 有時候根本就用不到).

Resource 的優點

圖片的生命周期可以得到管理無疑是 Resource ***的優點, 當我們需要圖片的時候就創建一個, 當我們不需要這個圖片的時候就讓他銷毀. 圖片不會長期的保存在內存當中, 所以不會有很多的內存浪費. 同時, 大圖一般不會長期使用, 而且大圖占用內存一般比小圖多了好多倍, 所以在減少大圖的內存占用中, Resource 做的非常好.

ImageAssets 與 “imageNamed:”

ImageAssets 的設計初衷主要是為了自動適配 Retina 屏幕和非 Retina 屏幕, 也就是解決 iPhone 4 和 iPhone 3GS 以及以前機型的屏幕適配問題. 現在 iPhone 3GS 以及之前的機型都已被淘汰, 非 Retina 屏幕已不再是開發考慮的范圍. 但是 plus 機型的推出將 Retina 屏幕又提高了一個水平, ImageAssets 現在的主要功能則是區分 plus 屏幕和非 plus 屏幕, 也就是解決 2 倍 Retina 屏幕和 3 倍 Retina 屏幕的視屏問題.

ImageAssets 的使用方式

iOS 開發中一般在工程內導入兩個到三個同內容不同像素的圖片文件, 一般如下:

  1. image.png (30 x 30)
  2. image@2x.png (60 x 60)
  3. image@3x.png (90 x 90)

這三張圖片都是相同內容, 而且圖片名稱的前綴相同, 區別在與圖片名以及圖片的分辨率. 開發者將這三張圖片拉入 ImageAssets 后, Xcode 會以圖片前綴創建一個圖片組(這里也就是 “image”). 然后在代碼中寫:

  1. UIImage *image = [UIImage imageNamed:@"image"]; 

就會根據不同屏幕來獲取對應不同的圖片數據來創建圖片. 如果是 3GS 之前的機型就會讀取 “image.png”, 普通 Retina 會讀取 “image@2x.png“, plus Retina 會讀取 “image@3x.png“, 如果某一個文件不存在, 就會用另一個分辨率的圖片代替之.

ImageAssets 的特性

與 Resources 相似, ImageAssets 也是從圖片文件中讀取圖片數據轉為 UIImage, 只不過這些圖片數據都打包在 ImageAssets 中. 還有一個***的區別就是圖片緩存. 相當于有一個字典, key 是圖片名, value是圖片對象. 調用imageNamed:方法時候先從這個字典里取, 如果取到就直接返回, 如果取不到再去文件中創建, 然后保存到這個字典后再返回. 由于字典的key和value都是強引用, 所以一旦創建后的圖片永不銷毀.

其內部代碼相似于:

  1. + (NSMutableDictionary *)imageBuff { 
  2.  
  3.     static NSMutableDictionary *_imageBuff; 
  4.  
  5.     static dispatch_once_t onceToken; 
  6.  
  7.     dispatch_once(&onceToken, ^{ 
  8.  
  9.         _imageBuff = [[NSMutableDictionary alloc] init]; 
  10.  
  11.     }); 
  12.  
  13.     return _imageBuff; 
  14.  
  15.  
  16.   
  17.  
  18. + (instancetype)imageNamed:(NSString *)imageName { 
  19.  
  20.     if (!imageName) { 
  21.  
  22.         return nil; 
  23.  
  24.     } 
  25.  
  26.     UIImage *image = self.imageBuff[imageName]; 
  27.  
  28.     if (image) { 
  29.  
  30.         return image; 
  31.  
  32.     } 
  33.  
  34.     NSString *path = @"this is the image path"//這段邏輯忽略 
  35.  
  36.     image = [self imageWithContentsOfFile:path]; 
  37.  
  38.     if (image) { 
  39.  
  40.         self.imageBuff[imageName] = image; 
  41.  
  42.     } 
  43.  
  44.     return image; 
  45.  
  46.  

ImageAssets 的使用場景

ImageAssets 最主要的使用場景就是 icon 類的圖片, 一般 icon 類的圖片大小在 3kb 到 20 kb 不等, 都是一些小文件.

ImageAssets 的優點

當一個 icon 在多個地方需要被顯示的時候, 其對應的UIImage對象只會被創建一次, 而且多個地方的 icon 都將會共用一個 UIImage 對象. 減少沙盒的讀取操作. 

  1. + (YYImage *)imageNamed:(NSString *)name { 
  2.  
  3.     if (name.length == 0) return nil; 
  4.  
  5.     if ([name hasSuffix:@"/"]) return nil; 
  6.  
  7.   
  8.  
  9.     NSString *res = name.stringByDeletingPathExtension; 
  10.  
  11.     NSString *ext = name.pathExtension; 
  12.  
  13.     NSString *path = nil; 
  14.  
  15.     CGFloat scale = 1; 
  16.  
  17.   
  18.  
  19.     // If no extension, guess by system supported (same as UIImage). 
  20.  
  21.     NSArray *exts = ext.length > 0 ? @[ext] : @[@"", @"png", @"jpeg", @"jpg", @"gif", @"webp", @"apng"]; 
  22.  
  23.     NSArray *scales = [NSBundle preferredScales]; 
  24.  
  25.     for (int s = 0; s count; s++) { 
  26.  
  27.         scale = ((NSNumber *)scales[s]).floatValue; 
  28.  
  29.         NSString *scaledName = [res stringByAppendingNameScale:scale]; 
  30.  
  31.         for (NSString *e in exts) { 
  32.  
  33.             path = [[NSBundle mainBundle] pathForResource:scaledName ofType:e]; 
  34.  
  35.             if (path) break; 
  36.  
  37.         } 
  38.  
  39.         if (path) break; 
  40.  
  41.     } 
  42.  
  43.     if (path.length == 0) return nil; 
  44.  
  45.   
  46.  
  47.     NSData *data = [NSData dataWithContentsOfFile:path]; 
  48.  
  49.     if (data.length == 0) return nil; 
  50.  
  51.   
  52.  
  53.     return [[self alloc] initWithData:data scale:scale]; 
  54.  
  55.  

UIImage 的內存問題

Resource 的缺點

當我們需要圖片的時候就會去沙盒中讀取這個圖片文件, 轉換成UIImage對象來使用. 現在假設一種場景:

  1. image@2x.png 圖片占用 5kb 的內存
  2. image@2x.png 在多個界面都用到, 且有7處會同時顯示這個圖片

通過代碼分析就可以知道 Resource 這個方式在這個情景下會占用 5kb/個 X 7個 = 35kb 內存. 然而, 在 ImageAssets 方式下, 全部取自字典緩存中的UIImage, 無論有幾處顯示圖片, 都只會占用 5kb/個 X 1個 = 5kb 內存. 此時 Resource 占用內存將會更大.

ImageAssets 的缺點

***次讀取的圖片保存到緩沖區, 然后永不銷毀. 如果這個圖片過大, 占用幾百 kb, 這一塊的內存將不會釋放, 必然導致內存的浪費, 而且這個浪費的周期與APP的生命周期同步.

解決方案

為了解決 Resource 的多圖共存問題, 可以學習 ImageAssets 中的字典來形成鍵值對, 當字典中name對應的image存在就不創建, 如果不存在就創建. 字典的存在必然導致 UIImage 永不銷毀, 所以還要考慮字典不會影響到 UIImage 的自動銷毀問題. 由此可以做出如下總結:

  1. 需要一個字典存儲已經創建的 Image 的 name-image 映射
  2. 當除了這個字典外, 沒有別的對象持有 image, 則從這個字典中刪除對應 name-image 映射

***個要求的實現方式很簡單, 接下來探討第二個要求.

首先可以考慮如何判斷除了字典外沒有別的對象持有 image? 字典是強引用 key 和 value 的, 當 image 放入字典的時候, image 的引用計數器就會 + 1. 我們可以判斷字典中的 image 的引用計數器是否為 1, 如果為 1 則可以判斷出目前只有字典持有這個 image, 因此可以從這個字典里刪除這個 image.

這樣即可提出一個方案 MRC+字典

我們還可以換一種思想, 字典是強引用容器, 字典存在必然導致內部value的引用計數器大于等于1. 如果字典是一個弱引用容器, 字典的存在并不會影響到內部value的引用計數器, 那么 image 的銷毀就不會因為字典而受到影響.

于是又有一個方案 弱引用字典

接下來對這兩個方案作深入的分析和實現:

方案一之 MRC+字典

該方案具體思路是: 找到一個合適的時機, 遍歷所有 value 的 引用計數器, 當某個 value 的引用計數器為 1 時候(說明只有字典持有這個image), 則刪除這個key-value對.

***步, 在ARC下獲取某個對象的引用計數器:

首先 ARC 下是不允許使用retainCount這個屬性的, 但是由于 ARC 的原理是編譯器自動為我們管理引用計數器, 所以就算是 ARC 環境下, 引用計數器也是 Enable 狀態, 并且仍然是利用引用計數器來管理內存. 所以我們可以使用 KVC 來獲取引用計數器:

  1. @implementation NSObject (MRC) 
  2.  
  3.   
  4.  
  5. // 無法直接重寫 retainCount 的方法, 所以加了一個前綴 
  6.  
  7. - (NSUInteger)obj_retainCount { 
  8.  
  9.     return [[self valueForKey:@"retainCount"] unsignedLongValue]; 
  10.  
  11.  
  12.   
  13.  
  14. @end  

第二步 遍歷 value的引用計數器

  1. // 由于遍歷鍵值對時候不能做添加和刪除操作, 所以把要刪除的key放到一個數組中 
  2.  
  3. NSMutableArray *keyArr = [NSMutableArray array]; 
  4.  
  5. [self.imageDic enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, NSObject * _Nonnull obj, BOOL * _Nonnull stop){ 
  6.  
  7.     NSInteger count = obj.obj_retainCount; 
  8.  
  9.     if(count == 2) {// 字典持有 + obj參數持有 = 2 
  10.  
  11.         [keyArr addObject:key]; 
  12.  
  13.     } 
  14.  
  15. }]; 
  16.  
  17. [keyArr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 
  18.  
  19.     [self.imageDic removeObjectForKey:obj]; 
  20.  
  21. }];  

然后處理遍歷時機. 選擇遍歷時機是一個很困難的, 不能因為遍歷而大量占有系統資源. 可以在每一次通過 name 創建(或者從字典中獲取)時候遍歷一次, 但這個方法有可能會長時間不調用(比如一個用戶在某一個界面上呆很久). 所以我們可以在每一次 runloop 到來時候來做一次遍歷, 同時我們還需要標記遍歷狀態, 防止第二次 runloop 到來時候***次的遍歷還沒結束就開始新的遍歷了(此時應該直接放棄第二次遍歷).代碼如下:

  1. CFRunLoopObserverRef oberver= CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(),kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { 
  2.  
  3.     if (activity == kCFRunLoopBeforeWaiting) { 
  4.  
  5.         static enuming = NO
  6.  
  7.         if (!enuming) { 
  8.  
  9.             enuming = YES; 
  10.  
  11.             // 這里是遍歷代碼 
  12.  
  13.             enuming = NO
  14.  
  15.         } 
  16.  
  17.     } 
  18.  
  19. }); 
  20.  
  21.   
  22.  
  23. CFRunLoopAddObserver(CFRunLoopGetMain(), oberver, kCFRunLoopCommonModes); 

 具體實現請看代碼.

方案二之 弱引用字典

在上面那個方案中, 會在每一次 runloop 到來之時開辟一個線程去遍歷鍵值對. 通常來說, 每一個 APP 創建的圖片個數很大, 所以遍歷鍵值對雖然不會阻塞主線程, 但仍然是一個非常耗時耗資源的工作.

弱引用容器是指基于NSArray, NSDictionary, NSSet的容器類, 該容器與這些類***的區別在于, 將對象放入容器中并不會改變對象的引用計數器, 同時容器是以一個弱引用指針指向這個對象, 當對象銷毀時自動從容器中刪除, 無需額外的操作.

目前常用的弱引用容器的實現方式是block封裝解封

利用block封裝一個對象, 且block中對象的持有操作是一個弱引用指針. 而后將block當做對象放入容器中. 容器直接持有block, 而不直接持有對象. 取對象時解包block即可得到對應對象.

***步 封裝與解封

  1. typedef id (^WeakReference)(void); 
  2.  
  3.   
  4.  
  5. WeakReference makeWeakReference(id object) { 
  6.  
  7.     __weak id weakref = object; 
  8.  
  9.     return ^{ 
  10.  
  11.         return weakref; 
  12.  
  13.     }; 
  14.  
  15.  
  16.   
  17.  
  18. id weakReferenceNonretainedObjectValue(WeakReference ref) { 
  19.  
  20.     return ref ? ref() : nil; 
  21.  
  22.  

第二步 改造原容器

  1. - (void)weak_setObject:(id)anObject forKey:(NSString *)aKey { 
  2.  
  3.     [self setObject:makeWeakReference(anObject) forKey:aKey]; 
  4.  
  5.  
  6.   
  7.  
  8. - (void)weak_setObjectWithDictionary:(NSDictionary *)dic { 
  9.  
  10.     for (NSString *key in dic.allKeys) { 
  11.  
  12.         [self setObject:makeWeakReference(dic[key]) forKey:key]; 
  13.  
  14.     } 
  15.  
  16.  
  17.   
  18.  
  19. - (id)weak_getObjectForKey:(NSString *)key { 
  20.  
  21.     return weakReferenceNonretainedObjectValue(self[key]); 
  22.  
  23.  

這樣就實現了一個弱引用字典, 之后用弱引用字典代替imageNamed:中的強引用字典即可. 

責任編輯:龐桂玉 來源: iOS大全
相關推薦

2018-07-23 09:26:08

iOS內存優化

2013-09-16 16:56:09

AndroidBitmap內存優化

2021-11-23 10:25:35

性能優化iOS App 啟動優化

2017-01-23 21:05:00

AndroidApp啟動優化

2024-12-31 00:00:15

2013-12-09 15:21:28

ASOApp Store

2010-08-10 10:00:57

Flex內存

2010-08-10 10:17:44

Flex內存

2011-07-28 10:01:19

IOS 內存優化

2023-10-12 07:43:45

2011-08-10 09:06:44

內存內存優化

2009-09-08 09:45:23

App Engine性

2017-03-14 18:48:06

Android性能優化內存優化

2015-05-30 10:04:24

線下公開課51CTO沙龍MDSA

2019-09-17 09:21:01

2022-07-05 08:41:03

Redis保存大數據

2018-06-14 09:35:35

2017-04-18 21:27:01

AndroidAPP構建速度

2022-06-10 15:37:24

愛奇藝App網絡

2010-07-22 11:09:33

SQL Server內
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久这里只有精品首页 | 中文字幕在线一区 | 久久国产亚洲 | 亚欧精品一区 | 亚洲精品一区二区三区中文字幕 | 欧美日韩三级 | 国产精品99 | 色一情一乱一伦一区二区三区 | 久久99深爱久久99精品 | 久久电影一区 | 日本欧美在线观看视频 | 古典武侠第一页久久777 | 国产成人精品免高潮在线观看 | 久久久久国产视频 | 久久国产精品首页 | 日韩视频成人 | 国产乱码精品一区二区三区五月婷 | 国产免费av在线 | 激情欧美一区二区三区 | 一区二区三区av | 国产成人99久久亚洲综合精品 | 日韩精品视频一区二区三区 | 国产四区 | 色视频欧美 | 亚洲精品二三区 | 亚洲婷婷一区 | 夜夜干夜夜操 | 欧美日韩国产在线观看 | 国产一区二区三区 | 日韩精品在线观看一区二区三区 | 国产男女视频网站 | 午夜影院 | 国产精品久久久久久久7电影 | 91免费在线看 | 羞羞色在线观看 | 69av网 | 99精品久久 | 中文字幕在线精品 | 国产视频一区二区 | 日日干日日射 | 91高清视频在线观看 |