iOS-性能優(yōu)化的個(gè)人思路

技 術(shù) 文 章 / 超 人


本文所有內(nèi)容都是經(jīng)過親測實(shí)踐過的,絕無虛假辱魁。內(nèi)容僅代表個(gè)人編寫文章時(shí)的技術(shù)水平和思維廣度翻伺,所以本文會(huì)隨著個(gè)人提升而不斷更新。如有錯(cuò)誤或者大神指教虹统,請留言弓坞,萬分感謝。

文本大綱

  • 邏輯優(yōu)化

    • 代碼封裝優(yōu)化
    • 代碼執(zhí)行效率優(yōu)化
  • 界面優(yōu)化

    • 離屏渲染優(yōu)化
    • 界面加載優(yōu)化

邏輯優(yōu)化

代碼封裝優(yōu)化

代碼的封裝優(yōu)化主要是細(xì)化代碼的功能车荔,每個(gè)功能單獨(dú)提取出來做成一個(gè)方法渡冻,當(dāng)其他地方需要用到同樣功能時(shí)直接調(diào)用該方法即可,無需寫重復(fù)代碼忧便,減少代碼量族吻,增加代碼的重用性,方便單元測試珠增。
例如:一個(gè)過濾輸入文本內(nèi)容的方法超歌,需要過濾特殊字符和表情

- (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巍举;
}

上面的方法雖然實(shí)現(xiàn)了需要的功能,但是卻顯不靈活凝垛。假如我只想過濾表情懊悯,只想過濾特殊字符或者想過濾其他的內(nèi)容蜓谋,則需要重新寫一個(gè)方法來滿足功能。但是功能內(nèi)部的代碼卻大致相同炭分。這樣就增加了代碼量且使得代碼看起來非常臃腫桃焕。
對上面的代碼進(jìn)行封裝優(yōu)化的方案有很多種,見仁見智欠窒,主要在思路而不在方法覆旭。
例如:我選擇把過濾表情單獨(dú)的提取出來,成一個(gè)根據(jù)正則表達(dá)式來過濾內(nèi)容的方法岖妄,而過濾特殊字符串提取出來型将,成一個(gè)根據(jù)傳入的字符來過濾內(nèi)容的方法。

/**
 根據(jù)正則表達(dá)式過濾文字
 
 @param string 需要校驗(yàn)的文字
 @param regexStr 用以校驗(yàn)的正則表達(dá)式
 @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;
}

/**
 根據(jù)傳入的字符過濾文本內(nèi)容
 
 @param string 需要過濾的原文本
 @param regexStr 需要過濾的字符內(nèi)容
 @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荐虐;
}

這樣是方法中的功能性單一七兜,但針對性卻不單一。大大的提高了代碼的重用性和單元測試福扬。
代碼的封裝很重要腕铸,體現(xiàn)程序員的編程思維的遠(yuǎn)見性,代碼的可擴(kuò)展性铛碑。在合作開發(fā)時(shí)狠裹,能方便他人

代碼執(zhí)行效率優(yōu)化
執(zhí)行效率的優(yōu)化主要在于得到結(jié)果的快慢,例如你想要一樣?xùn)|西汽烦,某寶和某東都有且價(jià)格差不多涛菠,但某寶要兩天才能拿到,而某東當(dāng)天下午就可以拿到撇吞。當(dāng)然大家都會(huì)某東啦...這就是效率的優(yōu)勢俗冻。
關(guān)于代碼的執(zhí)行效率其實(shí)還有很多地方,礙于本人目前的眼界和水平有限牍颈,后期會(huì)驗(yàn)證后添加更多

  • 效率1:我們最常用的for循環(huán)
    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循環(huán)用時(shí):%f",forEndTime);
    NSLog(@"forIn循環(huán)用時(shí):%f",forInEndTime);
    NSLog(@"enumerateKeysAndObjectsUsingBlock用時(shí):%f",enumerateEndTime);

執(zhí)行的結(jié)果:

for循環(huán)用時(shí):0.018385
forIn循環(huán)用時(shí):0.017044
enumerateKeysAndObjectsUsingBlock用時(shí):0.016417

看上去是enumerateKeysAndObjectsUsingBlock更快迄薄,但是執(zhí)行多次后 ,你會(huì)發(fā)現(xiàn)煮岁,有時(shí)for快有時(shí)forin快有時(shí)enumerateKeysAndObjectsUsingBlock快讥蔽。那是因?yàn)閿?shù)據(jù)量比較少。
假如把上面代碼里有100000個(gè)數(shù)據(jù)画机。
結(jié)果:

for循環(huán)用時(shí):20.812115
forIn循環(huán)用時(shí):21.940614
enumerateKeysAndObjectsUsingBlock用時(shí):23.253821

for循環(huán)明顯更快冶伞,不論嘗試多少次結(jié)果都是for循環(huán)明顯快。之所以用時(shí)20多秒是因?yàn)檠h(huán)內(nèi)部打印了日志色罚,因?yàn)榇蛴∪罩臼欠浅:臅r(shí)的操作。當(dāng)然可以不用在內(nèi)部去打印日志账劲。結(jié)果依然是for循環(huán)更快戳护。而往往在開發(fā)中金抡,for循環(huán)內(nèi)部執(zhí)行的操作都是比較多并且耗時(shí)的。
所以在數(shù)據(jù)量小時(shí)for腌且,forIn梗肝,enumerateKeysAndObjectsUsingBlock都可以。在大量數(shù)據(jù)時(shí)铺董,盡量用for循環(huán)去執(zhí)行巫击。經(jīng)過測試,執(zhí)行效率上NSDictionary < NSArray < NSSet 精续。NSSet的執(zhí)行效率最高

由此可見很多時(shí)候在獲取特定的數(shù)據(jù)時(shí)算法的選擇會(huì)即決定了代碼執(zhí)行次數(shù)也決定了執(zhí)行效率坝锰。作為一個(gè)開發(fā)者要了解最基本的各類算法

冒泡排序、快速排序重付、插入排序顷级、歸并排序、希爾排序确垫、動(dòng)態(tài)排序弓颈。這些都是提供執(zhí)行效率的基本算法。必須掌握了解的删掀,這里就不詳說了翔冀。不懂的朋友度娘


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

  • 需要了解的知識(shí)點(diǎn):
    屏幕顯示原理

    首先從過去的CRT顯示器原來說起纤子,CRT的電子槍是按照上圖的方式,從上到下一行一行掃描并曾現(xiàn)每幀畫面的付呕。為了把顯示器的顯示過程和系統(tǒng)的視頻控制器進(jìn)行同步计福,顯示器(或其他硬件)會(huì)用硬件時(shí)鐘產(chǎn)生一系列的信號(hào),當(dāng)電子槍換到新的一行徽职,準(zhǔn)備進(jìn)行掃描時(shí)象颖,顯示器會(huì)發(fā)送一個(gè)水平同步信號(hào)(horizonal synchronization 簡稱HSync),而當(dāng)一幀畫面繪制完成后姆钉,電子槍回復(fù)到原位準(zhǔn)備下一幀時(shí)说订,會(huì)發(fā)送一個(gè)垂直同步信號(hào)(vertical synchronization 簡稱VSync)。顯示器通常以固定頻率刷新潮瓶,而這個(gè)頻率就是VSync信號(hào)產(chǎn)生的頻率陶冷。盡管現(xiàn)在的設(shè)備都是用液晶顯示屏,但是原理仍然沒有改變毯辅。

目前IOS設(shè)備采用的是雙緩存+垂直同步埂伦,而Android在4.1后采用的是三緩存+垂直同步。
雙緩存機(jī)制:GPU會(huì)預(yù)先渲染好一幀放入下一個(gè)緩存區(qū)內(nèi)思恐,讓視頻控制器取出沾谜,當(dāng)下一幀渲染好后膊毁,GPU會(huì)直接把視頻控制器的指針指向第二個(gè)緩沖器,如此來提高效率基跑。即屏幕顯示一幀婚温,GPU預(yù)備下一幀。而不是顯示完一幀在計(jì)算下一幀媳否。


每一幀的由來栅螟,當(dāng)收到VSync信號(hào)后,首先篱竭,系統(tǒng)圖形服務(wù)會(huì)通過CADisplayLink等機(jī)制通知App力图,然后,App主線程開始在CPU中計(jì)算顯示內(nèi)容室抽,比如視圖等創(chuàng)建搪哪、布局、圖片解碼坪圾、文本繪制等晓折,接著,CPU計(jì)算好的內(nèi)容提交到GPU區(qū)兽泄,由GPU進(jìn)行變換漓概、合成、渲染病梢。隨后GPU將渲染結(jié)果提交到幀緩沖區(qū)胃珍,等到下一次收到VSync信號(hào)時(shí)顯示上一幀計(jì)算好的內(nèi)容。
界面卡頓蜓陌,則是所謂的幀丟失觅彰,當(dāng)在一個(gè)VSync信號(hào)內(nèi)(每秒60幀,每一幀1/60秒)钮热,CPU或者GPU沒有計(jì)算好內(nèi)容填抬,系統(tǒng)就會(huì)丟棄這一幀的內(nèi)容,而屏幕依然顯示的是之前的內(nèi)容隧期,就造成了界面卡頓飒责。
GPU屏幕渲染有以下兩種方式:

  • on-screen Rendering:在當(dāng)前屏幕渲染,指的是GPU的渲染操作實(shí)在當(dāng)前用于顯示的屏幕緩沖區(qū)進(jìn)行的仆潮;
  • off-screen Rendering:在當(dāng)前屏幕以外的區(qū)域渲染宏蛉,既離屏渲染,指的是在當(dāng)前顯示的屏幕緩沖區(qū)以外的區(qū)域開辟出來的一個(gè)新的緩沖區(qū)去進(jìn)行渲染操作性置。

由上面知識(shí)點(diǎn)拾并,可以看出離屏渲染是在GPU中造成的。

  • 離屏渲染: 所謂的離屏渲染即是在GPU計(jì)算時(shí)嗅义,由于界面層次復(fù)雜混合度大等造成計(jì)算的復(fù)雜度過大个榕,導(dǎo)致GPU需要重新創(chuàng)建一個(gè)額外的屏幕外緩沖區(qū)計(jì)算這個(gè)位圖武通。當(dāng)計(jì)算好后在轉(zhuǎn)換到幀緩沖區(qū)囚枪。這一次的渲染是脫離了屏幕而在屏幕以外的區(qū)域渲染完成的括勺,所以叫做離屏渲染。

創(chuàng)建額外的屏幕外緩沖區(qū)去計(jì)算位圖,再去替換屏幕內(nèi)容的代價(jià)是非常大且耗時(shí)的孵奶。解決離屏渲染是提升用戶體驗(yàn)非常重要的點(diǎn)谊囚,因?yàn)殡x屏渲染會(huì)導(dǎo)致幀丟失界面卡頓資源消耗。

補(bǔ)充知識(shí)點(diǎn):
UIView和CALayer的關(guān)系
The Relationship Between Layers and Views 這里有一篇關(guān)于它倆關(guān)系的詳細(xì)說明
簡單的說UIView是基于CALayer進(jìn)行封裝的究履。UIView的每個(gè)屬性都對應(yīng)這CALayer的一個(gè)屬性。

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

而CALayer負(fù)責(zé)顯示UIView的具體內(nèi)容爸黄,UIView負(fù)責(zé)提供內(nèi)容和處理響應(yīng)事件等梆奈,也就是說我們在手機(jī)上看見的都是CALayer所呈現(xiàn)的內(nèi)容。下面是CALayer的結(jié)構(gòu)圖

CALayer結(jié)構(gòu)圖

CALayer由三個(gè)視覺元素組成称开,background背景 亩钟、contents內(nèi)容乓梨、border邊框。而中間的contents的屬性聲明為var contents: AnyObject?實(shí)際上它必須是個(gè)CGImage才能顯示清酥。

造成離屏渲染的點(diǎn):

  • shouldRasterize(光柵化)

當(dāng)設(shè)置shouldRasterize = YES時(shí)扶镀,會(huì)把光柵化的圖片保存成一個(gè)bitmap緩存起來,當(dāng)下一次要顯示這個(gè)圖層時(shí)焰轻,CPU會(huì)直接從緩存中拿取位圖臭觉,傳給GPU,而不需要GPU再去渲染這一部分的圖層辱志,減少GPU的渲染計(jì)算胧谈。 可以通過Instruments core animation或者模擬器 中的 Color Hits Green and Misses Red來查看圖層是否被緩存了,綠色表示緩存荸频,紅色表示沒有緩存。一般視圖shouldRasterize默認(rèn)為NO客冈,對于經(jīng)常變換的視圖不要使用shouldRasterize旭从。會(huì)造成性能消耗的浪費(fèi)。
關(guān)于shouldRasterize是一個(gè)有取有舍的屬性场仲,對于那些復(fù)雜但是內(nèi)容不長變的視圖可以用shouldRasterize來緩存內(nèi)容和悦,減少GPU每次的計(jì)算,達(dá)到性能提高渠缕。但是要慎用鸽素,目前本人項(xiàng)目中還沒有使用過shouldRasterize來緩存內(nèi)容。

  • mask(遮罩層)

屏幕上的每一個(gè)像素點(diǎn)是由當(dāng)前像素點(diǎn)上多層layer通過GPU混合顏色計(jì)算出來的,視圖的layer一般在最下層亦鳞,陰影則在視圖layer之下馍忽。mask是layer的一個(gè)屬性,它也是CALayer類型的燕差,從官方對該屬性的注釋可知遭笋,默認(rèn)情況下mask是為nil不存在的。mask相當(dāng)于一個(gè)遮罩層徒探,覆蓋在視圖的layer的上層瓦呼,如果視圖的layer是contentLayer,那么為這個(gè)layer添加一個(gè)mask测暗,可以用mask來控制視圖顯示的區(qū)域和透明度央串。在mask的區(qū)域內(nèi)的contentLayer會(huì)被顯示,而之外的將不被顯示碗啄,而區(qū)域內(nèi)的contentLayer將通過mask層把像素顏色傳遞出去质和,如果mask的opacity不為1,那么mask將會(huì)按照opacity值過濾contentLayer的內(nèi)容稚字。當(dāng)為視圖設(shè)置了mask后侦另,mask的復(fù)雜度會(huì)決定GPU的計(jì)算復(fù)雜度,當(dāng)mask的opacity不為1時(shí)或者視圖的alpha不為1,那么GPU將進(jìn)行多層layer的混合顏色計(jì)算褒傅。

  • shadows(陰影)

陰影是直接合成一個(gè)在視圖下面的layer弃锐,而不是在下面創(chuàng)建一個(gè)新的視圖來當(dāng)做陰影,當(dāng)陰影的透明度不為1時(shí)殿托,它的渲染復(fù)雜度會(huì)比較大霹菊。

  • EdgeAnntialiasing(抗鋸齒)

allowsEdgeAntialiasing是ios7以后提供的方法,用來抗鋸齒支竹,有時(shí)候圖片縮放或者界面旋轉(zhuǎn)會(huì)造成邊框出現(xiàn)鋸齒旋廷。而鋸齒的計(jì)算是非常耗性能的會(huì)造成離屏渲染的。所以在出現(xiàn)鋸齒情況下allowsEdgeAntialiasing設(shè)置為YES

  • GroupOpacity(不透明)

allowsGroupOpacity是設(shè)置視圖子視圖在透明度上是否跟父視圖一樣礼搁,一般默認(rèn)情況下是為YES的饶碘。如果父視圖的透明度不為1,那么子視圖的透明度也不會(huì)為1馒吴。在GPU渲染的時(shí)候扎运,就會(huì)造成既要渲染子視圖還要渲染子視圖下面的父視圖內(nèi)容,然后合成視圖饮戳。這樣造成GPU計(jì)算復(fù)雜度增大需要離屏渲染解決豪治。

  • 復(fù)雜形狀比如圓角等

這里復(fù)雜形分為兩種
一種是有系統(tǒng)設(shè)置造成的形狀,比如設(shè)置圓角用maskToBundle加cornerRadius這種是有系統(tǒng)剪裁形成的圓角形狀扯罐。
另一種是繪制生成的形狀负拟,比如圖片中有圓角區(qū)域外是透明的或者直接繪制圓角。
系統(tǒng)形狀會(huì)造成GPU的消耗歹河,因?yàn)榧舨脮?huì)很耗性能掩浙,而繪制會(huì)造成CPU性能消耗高,因?yàn)槔L制工作是由CPU造成的

  • 漸變

漸變的渲染計(jì)算是非常負(fù)責(zé)好性能的秸歧。

  • Color Blended layers
    標(biāo)示混合的圖層會(huì)為紅色,不透明的圖層為綠色涣脚,通常我們希望綠色的區(qū)域越多越好。
    Color Hits Green and Misses Red
    假如我們設(shè)置viewlayer的shouldRasterize為YES寥茫,那些成功被緩存的layer會(huì)標(biāo)注為綠色遣蚀,反之為紅色,下面會(huì)有詳細(xì)介紹纱耻。
  • Color copied images
    標(biāo)示那些被Core Animation拷貝的圖片芭梯。這主要是因?yàn)樵搱D片的色彩格式不能被GPU直接處理,需要在CPU這邊做轉(zhuǎn)換弄喘,假如在主線層做這個(gè)操作對性能會(huì)有一定的影響玖喘。
  • Color misaligned images
    被縮放的圖片會(huì)被標(biāo)記為黃色,像素不對齊則會(huì)標(biāo)注為紫色。
  • Color offscreen-rendered yellow
    標(biāo)示哪些layer需要做離屏渲染(offscreen-render)

從上面的點(diǎn)相信你已經(jīng)了解到了造成離屏渲染的原因蘑志。
下面是關(guān)于離屏渲染累奈、界面優(yōu)化的方法

  • (1.)圓圖:
/* 思路:用不透明的mask來實(shí)現(xiàn)僅顯示圓角內(nèi)區(qū)域贬派。 之所以不采用異步繪制的方式是因?yàn)槔L制會(huì)消耗CPU性能,而且繪制需要考慮是否緩存澎媒,如果不緩存每次都需要繪制很耗電搞乏,但基于負(fù)載平衡的原理,有時(shí)也可以采用繪制來減少GPU壓力 */
 button.frame = CGRectMake(0, 0, 100, 100);
 button.backgroundColor = [UIColor redColor];
//顯示路徑戒努,根據(jù)UIRectCorner枚舉來控制那些區(qū)域需要圓角
 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;

本人在經(jīng)過各種測試和觀看各種文章資料后百思不得其解请敦,從原理上來說上面指出的離屏渲染的幾個(gè)點(diǎn)確實(shí)會(huì)造成離屏渲染。但是我代碼測試并查看Color Off-Screen Rendered储玫。居然沒有高亮黃色侍筛。。撒穷。納尼O灰!端礼!難道是蘋果又做了優(yōu)化了禽笑。。齐媒。正在查找蘋果文檔

接下來說說Color Blender Layer,在模擬器中旋轉(zhuǎn)Debug--> Color Blender Layer纷跛。模擬器界面中出現(xiàn)綠色的部分表示沒有透明內(nèi)容喻括,紅色的表示有透明內(nèi)容。對于好的程序來說贫奠,綠色越多越好唬血,上面離屏渲染講過了,透明會(huì)造成GPU的計(jì)算復(fù)雜度變大唤崭,需要混合顏色計(jì)算拷恨。下面來說說解決這個(gè)問題的方法

  • (2.)UILabel: 中如果顯示的文本是英文你會(huì)發(fā)現(xiàn)顯示的是綠色,然而換成中文后居然顯示的是紅色谢肾。
/* 普通的label只需要根據(jù)界面需求設(shè)置個(gè)背景顏色設(shè)置maskToBundle為YES,而button中的label把背景顏色設(shè)置成跟按鈕一個(gè)顏色設(shè)置maskToBundle為YES腕侄, */
label.background = [UIColor redColor];
label.maskToBundle = YES;
  • (3.)對于圖片中有透明區(qū)域,這就需要根據(jù)界面與設(shè)計(jì)同學(xué)進(jìn)行調(diào)整芦疏。雖然現(xiàn)在的處理器越來越強(qiáng)冕杠,這些優(yōu)化微不足道,但對于一個(gè)合格的程序員而言酸茴,盡善盡美才是追求

  • (4.)異步加載繪制
    知識(shí)點(diǎn):對象的創(chuàng)建分预,屬性的調(diào)整等都比較消耗CPU,所以盡量的使用輕量級的對象可以減少CPU的消耗薪捍,而CALayer的量級比UIVIew輕許多笼痹。所以數(shù)據(jù)或?qū)ο蟮膭?chuàng)建盡量放在異步線程中執(zhí)行配喳,保證主線程的暢通快速。但包含CALayer的控件都必須在主線程中創(chuàng)建操作凳干,而界面控件一般都是在viewDidLoad里創(chuàng)建的晴裹,而系統(tǒng)方法都是在主線程中執(zhí)行的,具體原因這里可以要說說Runloop的原理纺座,過段時(shí)間寫一篇關(guān)于Runloop原理的文章說明吧息拜。

/*如果viewDidLoad內(nèi)部代碼執(zhí)行耗時(shí)耗性會(huì)造成界面跳轉(zhuǎn)顯示卡頓,所以我采用異步主隊(duì)列方式讓控件的創(chuàng)建設(shè)置放在下一次MainRunloop的運(yùn)行中净响,這樣界面的跳轉(zhuǎn)會(huì)很流暢少欺。
*/
- (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.)界面的數(shù)據(jù)采用異步線程的方式去計(jì)算配置,當(dāng)界面數(shù)據(jù)都配置完全了馋贤,在回到主線程中去設(shè)置UI
(6.)在很多時(shí)候界面的數(shù)據(jù)我會(huì)需要從網(wǎng)絡(luò)中獲取赞别,而有時(shí)多個(gè)網(wǎng)絡(luò)請求之間沒有關(guān)聯(lián)關(guān)系,我們可以采用信號(hào)量的方式配乓,去同步請求網(wǎng)絡(luò)數(shù)據(jù)仿滔,當(dāng)所有網(wǎng)絡(luò)數(shù)據(jù)都返回后,在開始計(jì)算配置數(shù)據(jù)

/* 創(chuàng)建信號(hào)量 */
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
   
   dispatch_async(dispatch_get_global_queue(0, 0), ^{
   //這里面是網(wǎng)絡(luò)請求1
   //請求成功或者失敗后需要去發(fā)送信號(hào)量犹芹,告訴等待隊(duì)列已經(jīng)完成一個(gè)任務(wù)的等待
   dispatch_semaphore_signal(semaphore);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
   //這里面是網(wǎng)絡(luò)請求2
 //請求成功或者失敗后需要去發(fā)送信號(hào)量崎页,告訴等待隊(duì)列已經(jīng)完成一個(gè)任務(wù)的等待
   dispatch_semaphore_signal(semaphore);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
   //這里面是網(wǎng)絡(luò)請求3
 //請求成功或者失敗后需要去發(fā)送信號(hào)量,告訴等待隊(duì)列已經(jīng)完成一個(gè)任務(wù)的等待
   dispatch_semaphore_signal(semaphore);
});
       /* 有幾個(gè)任務(wù)就創(chuàng)建對少個(gè)信號(hào)等待 */
       dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
       dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
       dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
       /* 當(dāng)網(wǎng)絡(luò)數(shù)據(jù)都返回了腰埂,異步去配置計(jì)算界面最終顯示需要的數(shù)據(jù) */
     dispatch_async(dispatch_get_global_queue(0, 0), ^{
       //配置計(jì)算界面最終顯示需要的數(shù)據(jù)
       //數(shù)據(jù)配置完成后回到主線程更新UI  
     dispatch_async(dispatch_get_main_queue(), ^{
          //更新UI
       });
});

(7.)通過Storyboard創(chuàng)建的視圖對象消耗的資源比純代碼創(chuàng)建對象要多很多
(8.)Block回調(diào)來異步執(zhí)行任務(wù)回調(diào)(Block是個(gè)很神奇的東西飒焦,要靈活應(yīng)用啊)

//文本的寬高計(jì)算會(huì)占用很大一部分資源,所以盡量用異步線程去執(zhí)行操作屿笼,計(jì)算好后再回到主線程返回?cái)?shù)據(jù)牺荠。
/**
 計(jì)算文字寬高
 
 @param string (NSString *) 計(jì)算高度的字符串
 @param maxHeight (CGFloat) 最大高度[如果最大高度為0,表示無高度限制]
 @param maxWidth (CGFloat) 最大寬度
 @param textFont (UIFont *) 文字粗細(xì)度
 @param block (CGSize) 返回文字的size
 */
+(void)textBoundingRectWithString:(NSString *)string maxHeight:(CGFloat)maxHeight maxWidth:(CGFloat)maxWidth textFont:(UIFont *)textFont Block:(void (^)(CGSize obj))block
{
    /* 如果傳入內(nèi)容有誤驴一,直接返回結(jié)果到當(dāng)前線程*/
    if (!textFont || [self isBlankString:string] == YES) {
        if (block) {
            block(CGSizeMake(0, 0));
        }
      return;
    }
    /* 異步執(zhí)行計(jì)算操作*/
    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));
        }
        /* 計(jì)算完成后再主線程中回調(diào)數(shù)據(jù)休雌,因?yàn)橐话憷怪岛髸?huì)直接設(shè)置UI控件屬性。 */
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block) {
                block(lastSize);
            }
        });
    });
}
  • (9.)關(guān)于TableView的優(yōu)化請看我另外一篇文章UITableView的性能優(yōu)化

  • (10.)有次跟朋友討論優(yōu)化的時(shí)候肝断,說道為什么微博內(nèi)容多也復(fù)雜杈曲,流暢度這么高。我們改用的方法都用了胸懈,但是cell內(nèi)部內(nèi)容一復(fù)雜幀數(shù)就開始下降了鱼蝉。后來才知道,原來是自動(dòng)布局的鍋箫荡,再加上自己對文本內(nèi)容認(rèn)識(shí)深度不夠魁亦。布局是非常好性能資源的,有時(shí)為了性能少用Autolayouer技術(shù)和UILabel(但實(shí)際情況好像不可能羔挡,哇咔咔)洁奈。那么選擇一個(gè)好的自動(dòng)布局第三方尤為重要了间唉。微博可能是有一套非開源的布局方法吧(這里有個(gè)來自百度知道團(tuán)隊(duì)的開源項(xiàng)目可以看看代碼學(xué)習(xí)學(xué)習(xí):FDTemplateLayoutCell。)

  • (11.)圖片的縮放利术,UIImageView的尺寸最好跟Bundle里的原圖大小呈野,因?yàn)閳D片的縮放是非常耗性能的。在實(shí)際開發(fā)中印叁,需要適配不同的屏幕尺寸被冒,這個(gè)時(shí)候就需要與設(shè)計(jì)大神們好好溝通了。我們常在開發(fā)適配的時(shí)候轮蜕,會(huì)寫一個(gè)比例尺寸昨悼,界面在不同屏幕下的尺寸都是按照這個(gè)比例縮放的。所以要把自己的比例告訴設(shè)計(jì)大神們才能達(dá)到不縮放跃洛。

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

如果還是會(huì)縮放率触,那么你就需要異步去把圖片繪制成UIImageView大小的圖片了

/**
 根據(jù)邊距拉伸圖片
 
 @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.)避免不必要的圖片緩存:通常我們會(huì)用imageNamed:來加載圖片雾袱,但是這個(gè)方法會(huì)對圖片進(jìn)行緩存预柒。對于一些只有特定界面才有不常用的圖片用這個(gè)方法會(huì)造成一定的內(nèi)存消耗,一般不常用的圖片采用initWithContentsOfFile:掉伏∠噶牵可以自己寫一個(gè)UIImage的類別两曼,自行判斷使用哪一個(gè)方法。這個(gè)方法我有用過玻驻,但可能目前處理器性能太好或者設(shè)計(jì)同學(xué)的圖片本身就很小悼凑,內(nèi)存上并看不出多大差別。對于那些圖片為主的App這個(gè)方法還是很有用的

  • (13.)減少文件讀取次數(shù):文件的讀取是是否消耗資源的击狮,所以在沒有必要分開文件內(nèi)容的情況下佛析,盡量把內(nèi)容放在一個(gè)文件中益老,減少消耗彪蓬。例如圖片的讀取,第一種捺萌,多個(gè)標(biāo)簽圖片放在一個(gè)圖片中档冬,然后根據(jù)圖片進(jìn)行區(qū)域繪制,這樣就減少了對圖片的讀取時(shí)消耗CPU的性能桃纯,第二種shouldRasterize光柵化酷誓,在GPU渲染時(shí),直接取出上次的繪制內(nèi)容态坦,來減少文件的讀取和重新繪制盐数。


未完待續(xù)...

后期還有很多優(yōu)化內(nèi)容比如緩存數(shù)據(jù)等等

參考文章:
離屏渲染優(yōu)化詳解:實(shí)例示范+性能測試
iOS 保持界面流暢的技巧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市伞梯,隨后出現(xiàn)的幾起案子玫氢,更是在濱河造成了極大的恐慌帚屉,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漾峡,死亡現(xiàn)場離奇詭異攻旦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)生逸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進(jìn)店門牢屋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人槽袄,你說我怎么就攤上這事烙无。” “怎么了掰伸?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵皱炉,是天一觀的道長。 經(jīng)常有香客問我狮鸭,道長合搅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任歧蕉,我火速辦了婚禮灾部,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘惯退。我一直安慰自己赌髓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布催跪。 她就那樣靜靜地躺著锁蠕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪懊蒸。 梳的紋絲不亂的頭發(fā)上荣倾,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機(jī)與錄音骑丸,去河邊找鬼舌仍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛通危,可吹牛的內(nèi)容都是我干的铸豁。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼菊碟,長吁一口氣:“原來是場噩夢啊……” “哼节芥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起逆害,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤头镊,失蹤者是張志新(化名)和其女友劉穎增炭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拧晕,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡隙姿,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了厂捞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片输玷。...
    茶點(diǎn)故事閱讀 40,498評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖靡馁,靈堂內(nèi)的尸體忽然破棺而出欲鹏,到底是詐尸還是另有隱情,我是刑警寧澤臭墨,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布赔嚎,位于F島的核電站,受9級特大地震影響胧弛,放射性物質(zhì)發(fā)生泄漏尤误。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一结缚、第九天 我趴在偏房一處隱蔽的房頂上張望损晤。 院中可真熱鬧,春花似錦红竭、人聲如沸尤勋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽最冰。三九已至,卻和暖如春稀火,著一層夾襖步出監(jiān)牢的瞬間暖哨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工憾股, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鹿蜀,地道東北人箕慧。 一個(gè)月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓服球,卻偏偏與公主長得像,于是被迫代替她去往敵國和親颠焦。 傳聞我的和親對象是個(gè)殘疾皇子斩熊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評論 2 359