偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

App的內(nèi)存優(yōu)化

移動(dòng)開發(fā) iOS
文章的前篇主要是對(duì)兩種不同的UIImage工廠方法的分析, 羅列出這些工廠方法的內(nèi)存管理的優(yōu)缺點(diǎn)。文章的后篇是本文要說(shuō)明的重點(diǎn), 如何結(jié)合兩種工廠方法的優(yōu)點(diǎn)做更進(jìn)一步的節(jié)約內(nèi)存的管理。

[[183249]]

這篇文章是筆者在開發(fā)App過(guò)程中發(fā)現(xiàn)的一些內(nèi)存問(wèn)題, 然后學(xué)習(xí)了YYKit框架時(shí)候也發(fā)現(xiàn)了圖片的緩存處理 (YYKit 作者聯(lián)系了我, 說(shuō)明了YYKit重寫imageNamed:的目的不是為了內(nèi)存管理, 而是增加兼容性, 同時(shí)也是為了YYKit中的動(dòng)畫服務(wù)). 以下內(nèi)容是筆者在開發(fā)中做了一些實(shí)驗(yàn)以及總結(jié). 如有錯(cuò)誤望即時(shí)提出, 筆者會(huì)***時(shí)間改正.

文章的前篇主要是對(duì)兩種不同的UIImage工廠方法的分析, 羅列出這些工廠方法的內(nèi)存管理的優(yōu)缺點(diǎn).

文章的后篇是本文要說(shuō)明的重點(diǎn), 如何結(jié)合兩種工廠方法的優(yōu)點(diǎn)做更進(jìn)一步的節(jié)約內(nèi)存的管理.

PS

本文所說(shuō)的 Resource 是指使用imageWithContentsOfFile:創(chuàng)建圖片的圖片管理方式.

ImageAssets 是指使用imageNamed:創(chuàng)建圖片的圖片管理方式.

如果你對(duì)這兩個(gè)方法已經(jīng)了如指掌, 可以直接看UIImage 與 YYImage 的內(nèi)存問(wèn)題和后面的內(nèi)容

[TOC]

UIImage 的內(nèi)存處理

在實(shí)際的蘋果App開發(fā)中, 將圖片文件導(dǎo)入到工程中無(wú)非使用兩種方式. 一種是 Resource (我也不知道應(yīng)該稱呼什么,就這么叫吧),還有一種是 ImageAssets 形式存儲(chǔ)在一個(gè)圖片資源管理文件中. 這兩種方式都可以存儲(chǔ)任何形式的圖片文件, 但是都有各自的優(yōu)缺點(diǎn)在內(nèi). 接下來(lái)我們就來(lái)談?wù)勥@兩種圖片數(shù)據(jù)管理方式的優(yōu)缺點(diǎn).

Resource 與 “imageWithContentsOfFile:”

Resource 的使用方式

將文件直接拖入到工程目錄下, 并告訴Xcode打包項(xiàng)目時(shí)候把這些圖片文件打包進(jìn)去. 這樣在應(yīng)用的”.app”文件夾中就有這些圖片. 在項(xiàng)目中, 讀取這些圖片可以通過(guò)以下方式來(lái)獲取圖片文件并封裝成UIImge對(duì)象:

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

而底層的實(shí)現(xiàn)原理近似是:

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

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

Resource 的特性

在 Resource 的圖片管理方式中, 所有的圖片創(chuàng)建都是通過(guò)讀取文件數(shù)據(jù)得到的, 讀取一次文件數(shù)據(jù)就會(huì)產(chǎn)生一次NSData以及產(chǎn)生一個(gè)UIImage, 當(dāng)圖片創(chuàng)建好后銷毀對(duì)應(yīng)的NSData, 當(dāng)UIImage的引用計(jì)數(shù)器變?yōu)?的時(shí)候自動(dòng)銷毀UIImage. 這樣的話就可以保證圖片不會(huì)長(zhǎng)期地存在在內(nèi)存中.

Resource 的常用情景

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

Resource 的優(yōu)點(diǎn)

圖片的生命周期可以得到管理無(wú)疑是 Resource ***的優(yōu)點(diǎn), 當(dāng)我們需要圖片的時(shí)候就創(chuàng)建一個(gè), 當(dāng)我們不需要這個(gè)圖片的時(shí)候就讓他銷毀. 圖片不會(huì)長(zhǎng)期的保存在內(nèi)存當(dāng)中, 所以不會(huì)有很多的內(nèi)存浪費(fèi). 同時(shí), 大圖一般不會(huì)長(zhǎng)期使用, 而且大圖占用內(nèi)存一般比小圖多了好多倍, 所以在減少大圖的內(nèi)存占用中, Resource 做的非常好.

ImageAssets 與 “imageNamed:”

ImageAssets 的設(shè)計(jì)初衷主要是為了自動(dòng)適配 Retina 屏幕和非 Retina 屏幕, 也就是解決 iPhone 4 和 iPhone 3GS 以及以前機(jī)型的屏幕適配問(wèn)題. 現(xiàn)在 iPhone 3GS 以及之前的機(jī)型都已被淘汰, 非 Retina 屏幕已不再是開發(fā)考慮的范圍. 但是 plus 機(jī)型的推出將 Retina 屏幕又提高了一個(gè)水平, ImageAssets 現(xiàn)在的主要功能則是區(qū)分 plus 屏幕和非 plus 屏幕, 也就是解決 2 倍 Retina 屏幕和 3 倍 Retina 屏幕的視屏問(wèn)題.

ImageAssets 的使用方式

iOS 開發(fā)中一般在工程內(nèi)導(dǎo)入兩個(gè)到三個(gè)同內(nèi)容不同像素的圖片文件, 一般如下:

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

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

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

就會(huì)根據(jù)不同屏幕來(lái)獲取對(duì)應(yīng)不同的圖片數(shù)據(jù)來(lái)創(chuàng)建圖片. 如果是 3GS 之前的機(jī)型就會(huì)讀取 “image.png”, 普通 Retina 會(huì)讀取 “image@2x.png“, plus Retina 會(huì)讀取 “image@3x.png“, 如果某一個(gè)文件不存在, 就會(huì)用另一個(gè)分辨率的圖片代替之.

ImageAssets 的特性

與 Resources 相似, ImageAssets 也是從圖片文件中讀取圖片數(shù)據(jù)轉(zhuǎn)為 UIImage, 只不過(guò)這些圖片數(shù)據(jù)都打包在 ImageAssets 中. 還有一個(gè)***的區(qū)別就是圖片緩存. 相當(dāng)于有一個(gè)字典, key 是圖片名, value是圖片對(duì)象. 調(diào)用imageNamed:方法時(shí)候先從這個(gè)字典里取, 如果取到就直接返回, 如果取不到再去文件中創(chuàng)建, 然后保存到這個(gè)字典后再返回. 由于字典的key和value都是強(qiáng)引用, 所以一旦創(chuàng)建后的圖片永不銷毀.

其內(nèi)部代碼相似于:

  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 的使用場(chǎng)景

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

ImageAssets 的優(yōu)點(diǎn)

當(dāng)一個(gè) icon 在多個(gè)地方需要被顯示的時(shí)候, 其對(duì)應(yīng)的UIImage對(duì)象只會(huì)被創(chuàng)建一次, 而且多個(gè)地方的 icon 都將會(huì)共用一個(gè) UIImage 對(duì)象. 減少沙盒的讀取操作. 

  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 的內(nèi)存問(wèn)題

Resource 的缺點(diǎn)

當(dāng)我們需要圖片的時(shí)候就會(huì)去沙盒中讀取這個(gè)圖片文件, 轉(zhuǎn)換成UIImage對(duì)象來(lái)使用. 現(xiàn)在假設(shè)一種場(chǎng)景:

  1. image@2x.png 圖片占用 5kb 的內(nèi)存
  2. image@2x.png 在多個(gè)界面都用到, 且有7處會(huì)同時(shí)顯示這個(gè)圖片

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

ImageAssets 的缺點(diǎn)

***次讀取的圖片保存到緩沖區(qū), 然后永不銷毀. 如果這個(gè)圖片過(guò)大, 占用幾百 kb, 這一塊的內(nèi)存將不會(huì)釋放, 必然導(dǎo)致內(nèi)存的浪費(fèi), 而且這個(gè)浪費(fèi)的周期與APP的生命周期同步.

解決方案

為了解決 Resource 的多圖共存問(wèn)題, 可以學(xué)習(xí) ImageAssets 中的字典來(lái)形成鍵值對(duì), 當(dāng)字典中name對(duì)應(yīng)的image存在就不創(chuàng)建, 如果不存在就創(chuàng)建. 字典的存在必然導(dǎo)致 UIImage 永不銷毀, 所以還要考慮字典不會(huì)影響到 UIImage 的自動(dòng)銷毀問(wèn)題. 由此可以做出如下總結(jié):

  1. 需要一個(gè)字典存儲(chǔ)已經(jīng)創(chuàng)建的 Image 的 name-image 映射
  2. 當(dāng)除了這個(gè)字典外, 沒(méi)有別的對(duì)象持有 image, 則從這個(gè)字典中刪除對(duì)應(yīng) name-image 映射

***個(gè)要求的實(shí)現(xiàn)方式很簡(jiǎn)單, 接下來(lái)探討第二個(gè)要求.

首先可以考慮如何判斷除了字典外沒(méi)有別的對(duì)象持有 image? 字典是強(qiáng)引用 key 和 value 的, 當(dāng) image 放入字典的時(shí)候, image 的引用計(jì)數(shù)器就會(huì) + 1. 我們可以判斷字典中的 image 的引用計(jì)數(shù)器是否為 1, 如果為 1 則可以判斷出目前只有字典持有這個(gè) image, 因此可以從這個(gè)字典里刪除這個(gè) image.

這樣即可提出一個(gè)方案 MRC+字典

我們還可以換一種思想, 字典是強(qiáng)引用容器, 字典存在必然導(dǎo)致內(nèi)部value的引用計(jì)數(shù)器大于等于1. 如果字典是一個(gè)弱引用容器, 字典的存在并不會(huì)影響到內(nèi)部value的引用計(jì)數(shù)器, 那么 image 的銷毀就不會(huì)因?yàn)樽值涠艿接绊?

于是又有一個(gè)方案 弱引用字典

接下來(lái)對(duì)這兩個(gè)方案作深入的分析和實(shí)現(xiàn):

方案一之 MRC+字典

該方案具體思路是: 找到一個(gè)合適的時(shí)機(jī), 遍歷所有 value 的 引用計(jì)數(shù)器, 當(dāng)某個(gè) value 的引用計(jì)數(shù)器為 1 時(shí)候(說(shuō)明只有字典持有這個(gè)image), 則刪除這個(gè)key-value對(duì).

***步, 在ARC下獲取某個(gè)對(duì)象的引用計(jì)數(shù)器:

首先 ARC 下是不允許使用retainCount這個(gè)屬性的, 但是由于 ARC 的原理是編譯器自動(dòng)為我們管理引用計(jì)數(shù)器, 所以就算是 ARC 環(huán)境下, 引用計(jì)數(shù)器也是 Enable 狀態(tài), 并且仍然是利用引用計(jì)數(shù)器來(lái)管理內(nèi)存. 所以我們可以使用 KVC 來(lái)獲取引用計(jì)數(shù)器:

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

第二步 遍歷 value的引用計(jì)數(shù)器

  1. // 由于遍歷鍵值對(duì)時(shí)候不能做添加和刪除操作, 所以把要?jiǎng)h除的key放到一個(gè)數(shù)組中 
  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參數(shù)持有 = 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. }];  

然后處理遍歷時(shí)機(jī). 選擇遍歷時(shí)機(jī)是一個(gè)很困難的, 不能因?yàn)楸闅v而大量占有系統(tǒng)資源. 可以在每一次通過(guò) name 創(chuàng)建(或者從字典中獲取)時(shí)候遍歷一次, 但這個(gè)方法有可能會(huì)長(zhǎng)時(shí)間不調(diào)用(比如一個(gè)用戶在某一個(gè)界面上呆很久). 所以我們可以在每一次 runloop 到來(lái)時(shí)候來(lái)做一次遍歷, 同時(shí)我們還需要標(biāo)記遍歷狀態(tài), 防止第二次 runloop 到來(lái)時(shí)候***次的遍歷還沒(méi)結(jié)束就開始新的遍歷了(此時(shí)應(yīng)該直接放棄第二次遍歷).代碼如下:

  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); 

 具體實(shí)現(xiàn)請(qǐng)看代碼.

方案二之 弱引用字典

在上面那個(gè)方案中, 會(huì)在每一次 runloop 到來(lái)之時(shí)開辟一個(gè)線程去遍歷鍵值對(duì). 通常來(lái)說(shuō), 每一個(gè) APP 創(chuàng)建的圖片個(gè)數(shù)很大, 所以遍歷鍵值對(duì)雖然不會(huì)阻塞主線程, 但仍然是一個(gè)非常耗時(shí)耗資源的工作.

弱引用容器是指基于NSArray, NSDictionary, NSSet的容器類, 該容器與這些類***的區(qū)別在于, 將對(duì)象放入容器中并不會(huì)改變對(duì)象的引用計(jì)數(shù)器, 同時(shí)容器是以一個(gè)弱引用指針指向這個(gè)對(duì)象, 當(dāng)對(duì)象銷毀時(shí)自動(dòng)從容器中刪除, 無(wú)需額外的操作.

目前常用的弱引用容器的實(shí)現(xiàn)方式是block封裝解封

利用block封裝一個(gè)對(duì)象, 且block中對(duì)象的持有操作是一個(gè)弱引用指針. 而后將block當(dāng)做對(duì)象放入容器中. 容器直接持有block, 而不直接持有對(duì)象. 取對(duì)象時(shí)解包block即可得到對(duì)應(yīng)對(duì)象.

***步 封裝與解封

  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.  

這樣就實(shí)現(xiàn)了一個(gè)弱引用字典, 之后用弱引用字典代替imageNamed:中的強(qiáng)引用字典即可. 

責(zé)任編輯:龐桂玉 來(lái)源: iOS大全
相關(guān)推薦

2018-07-23 09:26:08

iOS內(nèi)存優(yōu)化

2013-09-16 16:56:09

AndroidBitmap內(nèi)存優(yōu)化

2021-11-23 10:25:35

性能優(yōu)化iOS App 啟動(dòng)優(yōu)化

2017-01-23 21:05:00

AndroidApp啟動(dòng)優(yōu)化

2024-12-31 00:00:15

2013-12-09 15:21:28

ASOApp Store

2010-08-10 10:00:57

Flex內(nèi)存

2010-08-10 10:17:44

Flex內(nèi)存

2011-07-28 10:01:19

IOS 內(nèi)存優(yōu)化

2023-10-12 07:43:45

2011-08-10 09:06:44

內(nèi)存內(nèi)存優(yōu)化

2009-09-08 09:45:23

App Engine性

2015-05-30 10:04:24

線下公開課51CTO沙龍MDSA

2017-03-14 18:48:06

Android性能優(yōu)化內(nèi)存優(yōu)化

2022-07-05 08:41:03

Redis保存大數(shù)據(jù)

2019-09-17 09:21:01

2018-06-14 09:35:35

2017-04-18 21:27:01

AndroidAPP構(gòu)建速度

2022-06-10 15:37:24

愛奇藝App網(wǎng)絡(luò)

2010-07-22 11:09:33

SQL Server內(nèi)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)