1. 程式人生 > >ios 效能優化(一)

ios 效能優化(一)

  • 邏輯優化

    • 程式碼封裝優化
    • 程式碼執行效率優化
  • 介面優化

    • 離屏渲染優化
    • 介面載入優化

邏輯優化

程式碼封裝優化

程式碼的封裝優化主要是細化程式碼的功能,每個功能單獨提取出來做成一個方法,當其他地方需要用到同樣功能時直接呼叫該方法即可,無需寫重複程式碼,減少程式碼量,增加程式碼的重用性,方便單元測試。
例如:一個過濾輸入文字內容的方法,需要過濾特殊字元和表情

- (void)filterCharactorString:(NSString *)string
{
/*過濾表情*/
NSString *modifiedString;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"[^\\u0020-\\u007E\\u00A0-\\u00BE\\u2E80-\\uA4CF\\uF900-\\uFAFF\\uFE30-\\uFE4F\\uFF00-\\uFFEF\\u0080-\\u009F\\u2000-\\u201f\r\n]"options:NSRegularExpressionCaseInsensitive error:nil]; modifiedString = [regex stringByReplacingMatchesInString:string options:0 range:NSMakeRange(0, [text length]) withTemplate:@""]; /*過濾特殊字元*/ NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"@/:;()¥「」"、[]{}#%-*+=_\\|~<>$€^•'@#$%^&*()_+'\""]; int i = 0; while (i < modifiedString.length) { NSString *rangeString = [modifiedString substringWithRange:NSMakeRange(i, 1)]; NSRange range = [rangeString rangeOfCharacterFromSet:set]; if (range.length == 0) { modifiedString = [modifiedString stringByReplacingCharactersInRange:NSMakeRange(i, 1) withString:@""]; } i++; } return modifiedString; } 

上面的方法雖然實現了需要的功能,但是卻顯不靈活。假如我只想過濾表情,只想過濾特殊字元或者想過濾其他的內容,則需要重新寫一個方法來滿足功能。但是功能內部的程式碼卻大致相同。這樣就增加了程式碼量且使得程式碼看起來非常臃腫。
對上面的程式碼進行封裝優化的方案有很多種,見仁見智,主要在思路而不在方法。
例如:我選擇把過濾表情單獨的提取出來,成一個根據正則表示式來過濾內容的方法,而過濾特殊字串提取出來,成一個根據傳入的字元來過濾內容的方法。

/**
 根據正則表示式過濾文字
 
 @param string 需要校驗的文字
 @param regexStr 用以校驗的正則表示式
 @return 過濾後的文字
 */
+ (NSString *)filterCharactor:(NSString *)string withRegex:(NSString *)regexStr
{
    NSString *searchText = string; NSError *error = NULL; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regexStr options:NSRegularExpressionCaseInsensitive error:&error]; NSString *result = [regex stringByReplacingMatchesInString:searchText options:NSMatchingReportCompletion range:NSMakeRange(0, searchText.length) withTemplate:@""]; return result; } /** 根據傳入的字元過濾文字內容 @param string 需要過濾的原文字 @param regexStr 需要過濾的字元內容 @return 過濾後的文字 */ + (NSString *)filterSymbol:(NSString *)string withRegex:(NSString *)regexStr { NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:regexStr]; int i = 0; while (i < string.length) { NSString *rangeString = [string substringWithRange:NSMakeRange(i, 1)]; NSRange range = [rangeString rangeOfCharacterFromSet:set]; if (range.length == 0) { string = [string stringByReplacingCharactersInRange:NSMakeRange(i, 1) withString:@""]; } i++; } return string; } 

這樣是方法中的功能性單一,但針對性卻不單一。大大的提高了程式碼的重用性和單元測試。
程式碼的封裝很重要,體現程式設計師的程式設計思維的遠見性,程式碼的可擴充套件性。在合作開發時,能方便他人

程式碼執行效率優化
執行效率的優化主要在於得到結果的快慢,例如你想要一樣東西,某寶和某東都有且價格差不多,但某寶要兩天才能拿到,而某東當天下午就可以拿到。當然大家都會某東啦...這就是效率的優勢。
關於程式碼的執行效率其實還有很多地方,礙於本人目前的眼界和水平有限,後期會驗證後新增更多

  • 效率1:我們最常用的for迴圈
    NSMutableDictionary *dic = [NSMutableDictionary new];
    
    for (int i = 0 ; i < 100; i++) { [dic setObject:[NSString stringWithFormat:@"%i",i] forKey:[NSString stringWithFormat:@"%i",i]]; } CFAbsoluteTime forStarTime = CFAbsoluteTimeGetCurrent(); NSArray *dicValueArray = dic.allValues; for (int i = 0; i < dicValueArray.count; i++) { NSString *value = dicValueArray[i]; NSLog(@"for----value:%@",value); } CFAbsoluteTime forEndTime = CFAbsoluteTimeGetCurrent() - forStarTime; CFAbsoluteTime forInStarTime = CFAbsoluteTimeGetCurrent(); for (NSString *value in dic.allValues) { NSLog(@"forIn----value:%@",value); } CFAbsoluteTime forInEndTime = CFAbsoluteTimeGetCurrent() - forInStarTime; CFAbsoluteTime enumerateInStarTime = CFAbsoluteTimeGetCurrent(); [dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { NSLog(@"en----value:%@",obj); }]; CFAbsoluteTime enumerateEndTime = CFAbsoluteTimeGetCurrent() - enumerateInStarTime; NSLog(@"for迴圈用時:%f",forEndTime); NSLog(@"forIn迴圈用時:%f",forInEndTime); NSLog(@"enumerateKeysAndObjectsUsingBlock用時:%f",enumerateEndTime); 

執行的結果:

for迴圈用時:0.018385
forIn迴圈用時:0.017044
enumerateKeysAndObjectsUsingBlock用時:0.016417 

看上去是enumerateKeysAndObjectsUsingBlock更快,但是執行多次後 ,你會發現,有時for快有時forin快有時enumerateKeysAndObjectsUsingBlock快。那是因為資料量比較少。
假如把上面程式碼裡有100000個數據。
結果:

for迴圈用時:20.812115
forIn迴圈用時:21.940614
enumerateKeysAndObjectsUsingBlock用時:23.253821 

for迴圈明顯更快,不論嘗試多少次結果都是for迴圈明顯快。之所以用時20多秒是因為迴圈內部列印了日誌,因為列印日誌是非常耗時的操作。當然可以不用在內部去列印日誌。結果依然是for迴圈更快。而往往在開發中,for迴圈內部執行的操作都是比較多並且耗時的。
所以在資料量小時for,forIn,enumerateKeysAndObjectsUsingBlock都可以。在大量資料時,儘量用for迴圈去執行。經過測試,執行效率上NSDictionary < NSArray < NSSet 。NSSet的執行效率最高

由此可見很多時候在獲取特定的資料時演算法的選擇會即決定了程式碼執行次數也決定了執行效率。作為一個開發者要了解最基本的各類演算法

氣泡排序、快速排序、插入排序、歸併排序、希爾排序、動態排序。這些都是提供執行效率的基本演算法。必須掌握瞭解的,這裡就不詳說了。不懂的朋友度娘


離屏渲染
說到離屏渲染,需要先了解螢幕每一幀介面是如何得到的。

  • 需要了解的知識點:
      螢幕顯示原理
    首先從過去的CRT顯示器原來說起,CRT的電子槍是按照上圖的方式,從上到下一行一行掃描並曾現每幀畫面的。為了把顯示器的顯示過程和系統的視訊控制器進行同步,顯示器(或其他硬體)會用硬體時鐘產生一系列的訊號,當電子槍換到新的一行,準備進行掃描時,顯示器會發送一個水平同步訊號(horizonal synchronization 簡稱HSync),而當一幀畫面繪製完成後,電子槍回覆到原位準備下一幀時,會發送一個垂直同步訊號(vertical synchronization 簡稱VSync)。顯示器通常以固定頻率重新整理,而這個頻率就是VSync訊號產生的頻率。儘管現在的裝置都是用液晶顯示屏,但是原理仍然沒有改變。

目前IOS裝置採用的是雙快取+垂直同步,而Android在4.1後採用的是三快取+垂直同步。
雙快取機制:GPU會預先渲染好一幀放入下一個快取區內,讓視訊控制器取出,當下一幀渲染好後,GPU會直接把視訊控制器的指標指向第二個緩衝器,如此來提高效率。即螢幕顯示一幀,GPU預備下一幀。而不是顯示完一幀在計算下一幀。

 

   
每一幀的由來,當收到VSync訊號後, 首先,系統圖形服務會通過CADisplayLink等機制通知App, 然後,App主執行緒開始在CPU中計算顯示內容,比如檢視等建立、佈局、圖片解碼、文字繪製等, 接著,CPU計算好的內容提交到GPU區,由GPU進行變換、合成、渲染。隨後GPU將渲染結果提交到幀緩衝區,等到下一次收到VSync訊號時顯示上一幀計算好的內容。
介面卡頓,則是所謂的幀丟失,當在一個VSync訊號內(每秒60幀,每一幀1/60秒),CPU或者GPU沒有計算好內容,系統就會丟棄這一幀的內容,而螢幕依然顯示的是之前的內容,就造成了介面卡頓。
GPU螢幕渲染有以下兩種方式:

 

  • on-screen Rendering:在當前螢幕渲染,指的是GPU的渲染操作實在當前用於顯示的螢幕緩衝區進行的;
  • off-screen Rendering:在當前螢幕以外的區域渲染,既離屏渲染,指的是在當前顯示的螢幕緩衝區以外的區域開闢出來的一個新的緩衝區去進行渲染操作。

由上面知識點,可以看出離屏渲染是在GPU中造成的。

  • 離屏渲染:所謂的離屏渲染即是在GPU計算時,由於介面層次複雜混合度大等造成計算的複雜度過大,導致GPU需要重新建立一個額外的螢幕外緩衝區計算這個點陣圖。當計算好後在轉換到幀緩衝區。這一次的渲染是脫離了螢幕而在螢幕以外的區域渲染完成的,所以叫做離屏渲染。

建立額外的螢幕外緩衝區去計算點陣圖,再去替換螢幕內容的代價是非常大且耗時的。解決離屏渲染是提升使用者體驗非常重要的點,因為離屏渲染會導致幀丟失介面卡頓資源消耗。

補充知識點:
UIView和CALayer的關係
The Relationship Between Layers and Views這裡有一篇關於它倆關係的詳細說明
簡單的說UIView是基於CALayer進行封裝的。UIView的每個屬性都對應這CALayer的一個屬性。

  出自 WWDC 2012: iOS App Performance: Graphics and Animations

 

而CALayer負責顯示UIView的具體內容,UIView負責提供內容和處理響應事件等,也就是說我們在手機上看見的都是CALayer所呈現的內容。下面是CALayer的結構圖

  CALayer結構圖
CALayer由三個視覺元素組成, background背景 、 contents內容、 border邊框。而中間的contents的屬性宣告為 var contents: AnyObject?實際上它必須是個CGImage才能顯示。

 

造成離屏渲染的點:

  • shouldRasterize(光柵化)

當設定shouldRasterize = YES時,會把光柵化的圖片儲存成一個bitmap快取起來,當下一次要顯示這個圖層時,CPU會直接從快取中拿取點陣圖,傳給GPU,而不需要GPU再去渲染這一部分的圖層,減少GPU的渲染計算。 可以通過Instruments core animation或者模擬器 中的 Color Hits Green and Misses Red來檢視圖層是否被快取了,綠色表示快取,紅色表示沒有快取。一般檢視shouldRasterize預設為NO,對於經常變換的檢視不要使用shouldRasterize。會造成效能消耗的浪費。
關於shouldRasterize是一個有取有舍的屬性,對於那些複雜但是內容不長變的檢視可以用shouldRasterize來快取內容,減少GPU每次的計算,達到效能提高。但是要慎用,目前本人專案中還沒有使用過shouldRasterize來快取內容。

  • mask(遮罩層)

螢幕上的每一個畫素點是由當前畫素點上多層layer通過GPU混合顏色計算出來的,檢視的layer一般在最下層,陰影則在檢視layer之下。mask是layer的一個屬性,它也是CALayer型別的,從官方對該屬性的註釋可知,預設情況下mask是為nil不存在的。mask相當於一個遮罩層,覆蓋在檢視的layer的上層,如果檢視的layer是contentLayer,那麼為這個layer新增一個mask,可以用mask來控制檢視顯示的區域和透明度。在mask的區域內的contentLayer會被顯示,而之外的將不被顯示,而區域內的contentLayer將通過mask層把畫素顏色傳遞出去,如果mask的opacity不為1,那麼mask將會按照opacity值過濾contentLayer的內容。當為檢視設定了mask後,mask的複雜度會決定GPU的計算複雜度,當mask的opacity不為1時或者檢視的alpha不為1,那麼GPU將進行多層layer的混合顏色計算。

  • shadows(陰影)

陰影是直接合成一個在檢視下面的layer,而不是在下面建立一個新的檢視來當做陰影,當陰影的透明度不為1時,它的渲染複雜度會比較大。

  • EdgeAnntialiasing(抗鋸齒)

allowsEdgeAntialiasing是ios7以後提供的方法,用來抗鋸齒,有時候圖片縮放或者介面旋轉會造成邊框出現鋸齒。而鋸齒的計算是非常耗效能的會造成離屏渲染的。所以在出現鋸齒情況下allowsEdgeAntialiasing設定為YES

  • GroupOpacity(不透明)

allowsGroupOpacity是設定檢視子檢視在透明度上是否跟父檢視一樣,一般預設情況下是為YES的。如果父檢視的透明度不為1,那麼子檢視的透明度也不會為1。在GPU渲染的時候,就會造成既要渲染子檢視還要渲染子檢視下面的父檢視內容,然後合成檢視。這樣造成GPU計算複雜度增大需要離屏渲染解決。

  • 複雜形狀比如圓角等

這裡複雜形分為兩種
一種是有系統設定造成的形狀,比如設定圓角用maskToBundle加cornerRadius這種是有系統剪裁形成的圓角形狀。
另一種是繪製生成的形狀,比如圖片中有圓角區域外是透明的或者直接繪製圓角。
系統形狀會造成GPU的消耗,因為剪裁會很耗效能,而繪製會造成CPU效能消耗高,因為繪製工作是由CPU造成的

  • 漸變

漸變的渲染計算是非常負責好效能的。

  • Color Blended layers
    標示混合的圖層會為紅色,不透明的圖層為綠色,通常我們希望綠色的區域越多越好。
    Color Hits Green and Misses Red
    假如我們設定viewlayer的shouldRasterize為YES,那些成功被快取的layer會標註為綠色,反之為紅色,下面會有詳細介紹。
  • Color copied images
    標示那些被Core Animation拷貝的圖片。這主要是因為該圖片的色彩格式不能被GPU直接處理,需要在CPU這邊做轉換,假如在主線層做這個操作對效能會有一定的影響。
  • Color misaligned images
    被縮放的圖片會被標記為黃色,畫素不對齊則會標註為紫色。
  • Color offscreen-rendered yellow
    標示哪些layer需要做離屏渲染(offscreen-render)

從上面的點相信你已經瞭解到了造成離屏渲染的原因。
下面是關於離屏渲染、介面優化的方法

  • (1.)圓圖:
/* 思路:用不透明的mask來實現僅顯示圓角內區域。 之所以不採用非同步繪製的方式是因為繪製會消耗CPU效能,而且繪製需要考慮是否快取,如果不快取每次都需要繪製很耗電,但基於負載平衡的原理,有時也可以採用繪製來減少GPU壓力 */
 button.frame = CGRectMake(0, 0, 100, 100); button.backgroundColor = [UIColor redColor]; //顯示路徑,根據UIRectCorner列舉來控制那些區域需要圓角 UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 100*scale, 100*scale) byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(5, 5)]; CAShapeLayer *mask = [[CAShapeLayer alloc] init]; mask.path = path.CGPath; mask.frame = CGRectMake(0, 0, 100, 100); button.layer.mask = mask2; 

本人在經過各種測試和觀看各種文章資料後百思不得其解,從原理上來說上面指出的離屏渲染的幾個點確實會造成離屏渲染。但是我程式碼測試並檢視Color Off-Screen Rendered。居然沒有高亮黃色。。。納尼!!!難道是蘋果又做了優化了。。。正在查詢蘋果文件

接下來說說Color Blender Layer,在模擬器中旋轉Debug--> Color Blender Layer。模擬器介面中出現綠色的部分表示沒有透明內容,紅色的表示有透明內容。對於好的程式來說,綠色越多越好,上面離屏渲染講過了,透明會造成GPU的計算複雜度變大,需要混合顏色計算。下面來說說解決這個問題的方法

  • (2.)UILabel:中如果顯示的文字是英文你會發現顯示的是綠色,然而換成中文後居然顯示的是紅色。
/* 普通的label只需要根據介面需求設定個背景顏色設定maskToBundle為YES,而button中的label把背景顏色設定成跟按鈕一個顏色設定maskToBundle為YES, */
label.background = [UIColor redColor];
label.maskToBundle = YES;
  • (3.)對於圖片中有透明區域,這就需要根據介面與設計同學進行調整。雖然現在的處理器越來越強,這些優化微不足道,但對於一個合格的程式設計師而言,盡善盡美才是追求

  • (4.)非同步載入繪製
    知識點:物件的建立,屬性的調整等都比較消耗CPU,所以儘量的使用輕量級的物件可以減少CPU的消耗,而CALayer的量級比UIVIew輕許多。所以資料或物件的建立儘量放在非同步執行緒中執行,保證主執行緒的暢通快速。但包含CALayer的控制元件都必須在主執行緒中建立操作,而介面控制元件一般都是在viewDidLoad裡建立的,而系統方法都是在主執行緒中執行的,具體原因這裡可以要說說Runloop的原理,過段時間寫一篇關於Runloop原理的文章說明吧。

/*如果viewDidLoad內部程式碼執行耗時耗性會造成介面跳轉顯示卡頓,所以我採用非同步主佇列方式讓控制元件的建立設定放在下一次MainRunloop的執行中,這樣介面的跳轉會很流暢。
*/
- (void)viewDidLoad
{
    [super viewDidLoad];
dispatch_async(dispatch_get_main_queue(), ^{
    /* alphaButton */ self.alphaButton = [[UIButton alloc] init]; self.alphaButton.frame = CGRectMake((V_width - 100*scale)/2, 100*scale, 100*scale, 100*scale); self.alphaButton.backgroundColor = [UIColor redColor]; self.alphaButton.alpha = 0.5; [self.alphaButton setTitle:@"透明按鈕" forState:UIControlStateNormal]; [self.view addSubview:self.alphaButton]; }): } 

(5.)介面的資料採用非同步執行緒的方式去計算配置,當介面資料都配置完全了,在回到主執行緒中去設定UI
(6.)在很多時候介面的資料我會需要從網路中獲取,而有時多個網路請求之間沒有關聯關係,我們可以採用訊號量的方式,去同步請求網路資料,當所有網路資料都返回後,在開始計算配置資料

/* 建立訊號量 */
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
   
   dispatch_async(dispatch_get_global_queue(0, 0), ^{ //這裡面是網路請求1 //請求成功或者失敗後需要去傳送訊號量,告訴等待佇列已經完成一個任務的等待 dispatch_semaphore_signal(semaphore); }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ //這裡面是網路請求2 //請求成功或者失敗後需要去傳送訊號量,告訴等待佇列已經完成一個任務的等待 dispatch_semaphore_signal(semaphore); }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ //這裡面是網路請求3 //請求成功或者失敗後需要去傳送訊號量,告訴等待佇列已經完成一個任務的等待 dispatch_semaphore_signal(semaphore); }); /* 有幾個任務就建立對少個訊號等待 */ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); /* 當網路資料都返回了,非同步去配置計算介面最終顯示需要的資料 */ dispatch_async(dispatch_get_global_queue(0, 0), ^{ //配置計算介面最終顯示需要的資料 //資料配置完成後回到主執行緒更新UI dispatch_async(dispatch_get_main_queue(), ^{ //更新UI }); }); 

(7.)通過Storyboard建立的檢視物件消耗的資源比純程式碼建立物件要多很多
(8.)Block回撥來非同步執行任務回撥(Block是個很神奇的東西,要靈活應用啊)

//文字的寬高計算會佔用很大一部分資源,所以儘量用非同步執行緒去執行操作,計算好後再回到主執行緒返回資料。
/**
 計算文字寬高
 
 @param string (NSString *) 計算高度的字串
 @param maxHeight (CGFloat) 最大高度[如果最大高度為0,表示無高度限制]
 @param maxWidth (CGFloat) 最大寬度
 @param textFont (UIFont *) 文字粗細度
 @param block (CGSize) 返回文字的size
 */
+(void)textBoundingRectWithString:(NSString *)string maxHeight:(CGFloat)maxHeight maxWidth:(CGFloat)maxWidth textFont:(UIFont *)textFont Block:(void (^)(CGSize obj))block { /* 如果傳入內容有誤,直接返回結果到當前執行緒*/ if (!textFont || [self isBlankString:string] == YES) { if (block) { block(CGSizeMake(0, 0)); } return; } /* 非同步執行計算操作*/ dispatch_async(dispatch_get_global_queue(0, 0), ^{ CGSize lastSize; if (maxHeight == 0) { CGSize size = [string boundingRectWithSize:CGSizeMake(maxWidth, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:textFont} context:nil].size; lastSize = CGSizeMake(ceilf(size.width), ceilf(size.height)); }else { CGSize size = [string boundingRectWithSize:CGSizeMake(maxWidth, maxHeight) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:textFont} context:nil].size; lastSize = CGSizeMake(ceilf(size.width), ceilf(size.height)); } /* 計算完成後再主執行緒中回撥資料,因為一般拉倒值後會直接設定UI控制元件屬性。 */ dispatch_async(dispatch_get_main_queue(), ^{ if (block) { block(lastSize); } }); }); } 
  • (9.)關於TableView的優化請看我另外一篇文章UITableView的效能優化

  • (10.)有次跟朋友討論優化的時候,說道為什麼微博內容多也複雜,流暢度這麼高。我們改用的方法都用了,但是cell內部內容一複雜幀數就開始下降了。後來才知道,原來是自動佈局的鍋,再加上自己對文字內容認識深度不夠。佈局是非常好效能資源的,有時為了效能少用Autolayouer技術和UILabel(但實際情況好像不可能,哇咔咔)。那麼選擇一個好的自動佈局第三方尤為重要了。微博可能是有一套非開源的佈局方法吧(這裡有個來自百度知道團隊的開源專案可以看看程式碼學習學習:FDTemplateLayoutCell。)

  • (11.)圖片的縮放,UIImageView的尺寸最好跟Bundle裡的原圖大小,因為圖片的縮放是非常耗效能的。在實際開發中,需要適配不同的螢幕尺寸,這個時候就需要與設計大神們好好溝通了。我們常在開發適配的時候,會寫一個比例尺寸,介面在不同螢幕下的尺寸都是按照這個比例縮放的。所以要把自己的比例告訴設計大神們才能達到不縮放。

/* 這是我常用的比例 */
#define scale MIN([UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)/375.f

如果還是會縮放,那麼你就需要非同步去把圖片繪製成UIImageView大小的圖片了

/**
 根據邊距拉伸圖片
 
 @param sourceImage 原圖片
 @param edgeInsets 邊距
 @param resizingMode 縮放模式
 @param size 需要拉伸的大小
 @param block 處理後的圖片
 */
+(void)imageCompress:(UIImage *)sourceImage forEdgeInsets:(UIEdgeInsets)edgeInsets resizingMode:(UIImageResizingMode)resizingMode forSize:(CGSize)size Block:(void (^)(UIImage *image))block { /*非同步處理*/ dispatch_async(dispatch_get_global_queue(0, 0), ^{ UIImage *Image; Image = [sourceImage resizableImageWithCapInsets:edgeInsets resizingMode:resizingMode]; UIGraphicsBeginImageContext(CGSizeMake(size.width, size.height)); [Image drawInRect:CGRectMake(0,0,size.width, size.height)]; UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); if (block) { /* 回到主執行緒 */ dispatch_async(dispatch_get_main_queue(), ^{ block(newImage); }); } }); } 
  • (12.)避免不必要的圖片快取:通常我們會用imageNamed:來載入圖片,但是這個方法會對圖片進行快取。對於一些只有特定介面才有不常用的圖片用這個方法會造成一定的記憶體消耗,一般不常用的圖片採用initWithContentsOfFile:。可以自己寫一個UIImage的類別,自行判斷使用哪一個方法。這個方法我有用過,但可能目前處理器效能太好或者設計同學的圖片本身就很小,記憶體上並看不出多大差別。對於那些圖片為主的App這個方法還是很有用的

  • (13.)減少檔案讀取次數:檔案的讀取是是否消耗資源的,所以在沒有必要分開檔案內容的情況下,儘量把內容放在一個檔案中,減少消耗。例如圖片的讀取,第一種,多個標籤圖片放在一個圖片中,然後根據圖片進行區域繪製,這樣就減少了對圖片的讀取時消耗CPU的效能,第二種shouldRasterize光柵化,在GPU渲染時,直接取出上次的繪製內容,來減少檔案的讀取和重新繪製。



作者:樹下敲程式碼的超人
連結:https://www.jianshu.com/p/2efcf7ad2608
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。