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的一個屬性。
而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
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並註明出處。
ios 性能優化(一)