技 術(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è)屬性。
而CALayer負(fù)責(zé)顯示UIView的具體內(nèi)容爸黄,UIView負(fù)責(zé)提供內(nèi)容和處理響應(yīng)事件等梆奈,也就是說我們在手機(jī)上看見的都是CALayer所呈現(xiàn)的內(nèi)容。下面是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ù)等等