邏輯優化css
界面優化html
代碼封裝優化ios
代碼的封裝優化主要是細化代碼的功能,每一個功能單獨提取出來作成一個方法,當其餘地方須要用到一樣功能時直接調用該方法便可,無需寫重複代碼,減小代碼量,增長代碼的重用性,方便單元測試。
例如:一個過濾輸入文本內容的方法,須要過濾特殊字符和表情git
- (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; }
這樣是方法中的功能性單一,但針對性卻不單一。大大的提升了代碼的重用性和單元測試。
代碼的封裝很重要,體現程序員的編程思惟的遠見性,代碼的可擴展性。在合做開發時,能方便他人github
代碼執行效率優化
執行效率的優化主要在於獲得結果的快慢,例如你想要同樣東西,某寶和某東都有且價格差很少,但某寶要兩天才能拿到,而某東當天下午就能夠拿到。固然你們都會某東啦...這就是效率的優點。
關於代碼的執行效率其實還有不少地方,礙於本人目前的眼界和水平有限,後期會驗證後添加更多正則表達式
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的執行效率最高緩存
因而可知不少時候在獲取特定的數據時算法的選擇會即決定了代碼執行次數也決定了執行效率。做爲一個開發者要了解最基本的各種算法
冒泡排序、快速排序、插入排序、歸併排序、希爾排序、動態排序。這些都是提供執行效率的基本算法。必須掌握瞭解的,這裏就不詳說了。不懂的朋友度娘
離屏渲染
說到離屏渲染,須要先了解屏幕每一幀界面是如何獲得的。
目前IOS設備採用的是雙緩存+垂直同步,而Android在4.1後採用的是三緩存+垂直同步。
雙緩存機制:GPU會預先渲染好一幀放入下一個緩存區內,讓視頻控制器取出,當下一幀渲染好後,GPU會直接把視頻控制器的指針指向第二個緩衝器,如此來提升效率。即屏幕顯示一幀,GPU預備下一幀。而不是顯示完一幀在計算下一幀。
首先
,系統圖形服務會經過CADisplayLink等機制通知App,
而後
,App主線程開始在CPU中計算顯示內容,好比視圖等建立、佈局、圖片解碼、文本繪製等,
接着
,CPU計算好的內容提交到GPU區,由GPU進行變換、合成、渲染。隨後GPU將渲染結果提交到幀緩衝區,等到下一次收到VSync信號時顯示上一幀計算好的內容。
由上面知識點,能夠看出離屏渲染是在GPU中形成的。
屏幕外
緩衝區計算這個位圖。當計算好後在轉換到幀緩衝區。這一次的渲染是脫離了屏幕而在屏幕之外的區域渲染完成的,因此叫作離屏渲染。建立額外的屏幕外
緩衝區去計算位圖,再去替換屏幕內容的代價是很是大且耗時的。解決離屏渲染是提高用戶體驗很是重要的點,由於離屏渲染會致使幀丟失界面卡頓資源消耗。
補充知識點:
UIView和CALayer的關係
The Relationship Between Layers and Views這裏有一篇關於它倆關係的詳細說明
簡單的說UIView是基於CALayer進行封裝的。UIView的每一個屬性都對應這CALayer的一個屬性。
而CALayer負責顯示UIView的具體內容,UIView負責提供內容和處理響應事件等,也就是說咱們在手機上看見的都是CALayer所呈現的內容。下面是CALayer的結構圖
background
背景 、
contents
內容、
border
邊框。而中間的contents的屬性聲明爲
var contents: AnyObject?
實際上它必須是個CGImage才能顯示。
形成離屏渲染的點:
當設置shouldRasterize = YES時,會把光柵化的圖片保存成一個bitmap緩存起來,當下一次要顯示這個圖層時,CPU會直接從緩存中拿取位圖,傳給GPU,而不須要GPU再去渲染這一部分的圖層,減小GPU的渲染計算。 能夠經過Instruments core animation或者模擬器 中的 Color Hits Green and Misses Red來查看圖層是否被緩存了,綠色表示緩存,紅色表示沒有緩存。通常視圖shouldRasterize默認爲NO,對於常常變換的視圖不要使用shouldRasterize。會形成性能消耗的浪費。
關於shouldRasterize是一個有取有舍的屬性,對於那些複雜可是內容不長變的視圖能夠用shouldRasterize來緩存內容,減小GPU每次的計算,達到性能提升。可是要慎用,目前本人項目中尚未使用過shouldRasterize來緩存內容。
屏幕上的每個像素點是由當前像素點上多層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的混合顏色計算。
陰影是直接合成一個在視圖下面的layer,而不是在下面建立一個新的視圖來當作陰影,當陰影的透明度不爲1時,它的渲染複雜度會比較大。
allowsEdgeAntialiasing是ios7之後提供的方法,用來抗鋸齒,有時候圖片縮放或者界面旋轉會形成邊框出現鋸齒。而鋸齒的計算是很是耗性能的會形成離屏渲染的。因此在出現鋸齒狀況下allowsEdgeAntialiasing設置爲YES
allowsGroupOpacity是設置視圖子視圖在透明度上是否跟父視圖同樣,通常默認狀況下是爲YES的。若是父視圖的透明度不爲1,那麼子視圖的透明度也不會爲1。在GPU渲染的時候,就會形成既要渲染子視圖還要渲染子視圖下面的父視圖內容,而後合成視圖。這樣形成GPU計算複雜度增大須要離屏渲染解決。
這裏複雜形分爲兩種
一種是有系統設置形成的形狀,好比設置圓角用maskToBundle加cornerRadius這種是有系統剪裁造成的圓角形狀。
另外一種是繪製生成的形狀,好比圖片中有圓角區域外是透明的或者直接繪製圓角。
系統形狀會形成GPU的消耗,由於剪裁會很耗性能,而繪製會形成CPU性能消耗高,由於繪製工做是由CPU形成的
漸變的渲染計算是很是負責好性能的。
從上面的點相信你已經瞭解到了形成離屏渲染的緣由。
下面是關於離屏渲染、界面優化的方法
/* 思路:用不透明的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的計算複雜度變大,須要混合顏色計算。下面來講說解決這個問題的方法
/* 普通的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渲染時,直接取出上次的繪製內容,來減小文件的讀取和從新繪製。