最近被朋友問到的問題,總結(jié)了一下問題分享出來(鶸的分享通常都是一本正經(jīng)地胡說八道??),問題之間基本上沒什么關(guān)聯(lián),也沒有順序,純粹就是總結(jié)一下最近被問到的問題。
怎么看待視圖圓角的性能優(yōu)化?
視圖圓角的性能優(yōu)化并不是什么新鮮的東西,不知道為什么最近兩年特別火,我在網(wǎng)上也看了很多篇這方面的文章,感覺挺失望的,每篇文章的內(nèi)容基本上都是千篇一律,內(nèi)容基本上都是說,設(shè)置視圖的圓角(視圖設(shè)置圓角不一定會引起離屏渲染,這里默認(rèn)是會離屏渲染的情況)會引發(fā)GPU做離屏渲染的操作,GPU的使用率會大大增加,因此可能會出現(xiàn)屏幕掉幀的情況,所以要避免GPU的離屏渲染,轉(zhuǎn)交給CPU處理。每次看到這些內(nèi)容的時候我都想問,難道轉(zhuǎn)交給CPU之后,離屏渲染的工作就消失了嗎?我不是太明白為什么每篇文章基本上都是呼吁把離屏渲染轉(zhuǎn)交給CPU來完成,難道轉(zhuǎn)交給CPU來做就不會有性能問題?離屏渲染的工作,不是GPU就是CPU來完成,這本來就是一個折中的選擇,下面通過分析一個簡單的頁面來看看這種選擇(以下分析當(dāng)提到CPU處理時,忽略多線程優(yōu)化,均在主線程處理)。


首先我們需要簡單地分析這個頁面的資源分布情況。CPU的基本工作:創(chuàng)建視圖,視圖布局,文本繪制,圖片解壓加載;GPU的基本工作:頭像視圖的離屏渲染,渲雜整個頁面。按照這樣的分布,假設(shè)當(dāng)前頁面的CPU占用率是60%,而GPU占用率是25%,而屏幕并沒有掉幀但處于掉幀的邊緣,在這種情況下把GPU離屏渲染的工作轉(zhuǎn)交給CPU來完成后,CPU的占用率可能達(dá)到了70%,而GPU的占用率只有20%,原本處于掉幀的邊緣的狀況被打破了,因為CPU的壓力過大導(dǎo)致出現(xiàn)屏幕掉幀的情況,所以,面對這樣的情況,我們還是堅持要把離屏渲染的工作轉(zhuǎn)交給CPU來完成?當(dāng)然,這樣的假設(shè)可能很極端,實際情況可能要好得多,即使真的把離屏渲染的工作轉(zhuǎn)交給CPU來完成也不會出現(xiàn)掉幀(即使因為CPU而掉幀,還可以通過異步處理的方式進(jìn)行優(yōu)化),但是這樣的資源分配真的合理嗎?
要不要將GPU離屏渲染的工作轉(zhuǎn)交給CPU來完成,是需要根據(jù)實際情況折中選擇的,對于怎么選擇,我個人的標(biāo)準(zhǔn)是,首先要看當(dāng)前頁面是動態(tài)還是靜態(tài),如果只是一個靜態(tài)頁面,那就不需要去考慮性能優(yōu)化的問題;如果是一個動態(tài)頁面,要看這個頁面在程序中的位置,如果只是一個無關(guān)緊要且使用率低的頁面,也可以不去考慮掉幀問題;如果是一個很關(guān)鍵且使用率高的頁面,就需要先測試當(dāng)前頁面的性能瓶頸在于GPU還是CPU(怎么測試?網(wǎng)上有挺多這方面的資料,動手查一下吧),分析當(dāng)前頁面的資源分布情況,如果只是GPU的壓力大,可以將部分GPU的工作轉(zhuǎn)交給CPU來完成,如果是GPU和CPU的壓力都很大,那么就需要你進(jìn)行折中選擇的時候了,面對這種選擇,我不認(rèn)為會有一份明確的指南告訴你怎么做是對的,也不可能存在這樣的指南,因為任何的折中選擇都是權(quán)衡后的選擇,是否可以快速作出正確的選擇,我覺得這個時候就需要吃經(jīng)驗了,盡管如此,還是有一些經(jīng)驗可以作為參考(本文不展開講,以后可能會寫詳細(xì)的優(yōu)化文章)。
考慮當(dāng)前頁面是否有很多且需要快速響應(yīng)的CPU任務(wù)(如:點(diǎn)擊事件,必須在主線程處理的UI任務(wù)等),如果是,選擇其它方式優(yōu)化GPU或者選擇CPU并行處理的方式;考慮你自己或你的團(tuán)隊是否有能力駕馭并行處理(如果沒有,還是串行處理吧);考慮實現(xiàn)成本和價值(實現(xiàn)成本高的,你可能沒這時間,價值低的,實現(xiàn)了也沒太多意義);
怎么看待性能優(yōu)化?
性能優(yōu)化越來越流行,隨著iOS開發(fā)越來越成熟和穩(wěn)定,性能優(yōu)化更是被提到了日程,網(wǎng)上也出現(xiàn)了各式各樣的性能優(yōu)化技術(shù)文。就像上面所說的那樣,性能優(yōu)化是折中選擇,做性能優(yōu)化有很多好處,可以得升用戶體驗,可以更充分地利用資源等,但在你打算做性能優(yōu)化時,你不得不去考慮一些問題。
是否可以做性能優(yōu)化?當(dāng)你將要做的性能優(yōu)化會一定程度上破壞程序架構(gòu)或沖突時,你要怎么選擇(當(dāng)然是選擇做性能優(yōu)化咯,因為大部分的程序都沒有架構(gòu)設(shè)計這種東西??);
是否需要做性能優(yōu)化?需不需要做性能優(yōu)化,個人的標(biāo)準(zhǔn)是:基礎(chǔ),關(guān)鍵的模塊的性能優(yōu)化是需要且必要的(什么是基礎(chǔ),關(guān)鍵的模塊?舉個例子,UILabel在iOS8以前的底圖層是CALayer,在iOS8以后的底圖層是_UILabelLayer,這是蘋果對UILabel文本繪制的優(yōu)化),但對于業(yè)務(wù)層,特別是展示層的性能優(yōu)化,看模塊是否為迭代,增量式開發(fā)(一般情況下,以迭代,增量的方式來完成和完善功能的模塊都會有一個明確的戰(zhàn)略目標(biāo),會趨向穩(wěn)定),如果不是,建議不要花時間優(yōu)化這個模塊(除非你真的很有空,因為業(yè)務(wù)層的很多模塊是產(chǎn)品一拍腦門想出來的,產(chǎn)品本身就沒有明確的戰(zhàn)略目標(biāo)和方向,所以這個版本有,下次迭代可能就不要了)。
你的團(tuán)隊是否有能力做性能優(yōu)化?即使可以做,需要做性能優(yōu)化,最后還要看團(tuán)隊的實際情況,因為整個程序的優(yōu)化由一個人來完成太困難了,優(yōu)化所需的時間成本太高了,而且還可能會出現(xiàn),你做好的優(yōu)化被你的同事隨便就改了(多半的理由是:“那樣寫太麻煩,這樣寫直接調(diào)用多方便,還能一改整個項目都改了”,是,一改整個項目都改了,一改整個項目都崩了)。
性能優(yōu)化是折中選擇,做不做性能優(yōu)化也是折中選擇,這些選擇的標(biāo)準(zhǔn)也會隨著你經(jīng)驗的增加,知識面的擴(kuò)充,所在團(tuán)隊及角色的變化而有所不同。
怎么看待設(shè)計模式?
上周末有一個朋友很沮喪地跟我說:“我花了兩個月看完了GOF23,但這些東西真的是然并卵,完全想不到有什么例子,更想不到要怎么用”??赡苡泻芏嗳丝戳嗽O(shè)計模式之后都有同樣的感受,這些概念看上去好像挺有道理,但在實際開發(fā)中怎么應(yīng)用?學(xué)習(xí)設(shè)計模式對自身有什么幫助?在討論這些問題之前,我們先通過一些簡單的例子來看看系統(tǒng)中的設(shè)計模式。
首先我們來看看觸摸事件的整個過程(詳細(xì)過程可以點(diǎn)這里),當(dāng)觸摸事件從硬件系統(tǒng)發(fā)送到當(dāng)前Application之后,Application的主要操作分為兩步,查找第一響應(yīng)者,查找可以處理事件的響應(yīng)者。
查找第一響應(yīng)者,首先向Window對象發(fā)送hitTest消息,Window對象接收到消息后就開始進(jìn)行自身的檢測,通過pointInside方法來判斷觸摸點(diǎn)是否在本地坐標(biāo)系上(忽略像userInteractionEnabled等判斷),如果在,就向它的子視圖對象發(fā)送hitTest消息,子視圖對象再重復(fù)這一過程,直到最后一個符合條件的子視圖對象,并返回這個子視圖對象。
查找可以處理事件的響應(yīng)者,通過上一步獲取到第一響應(yīng)者,向第一響應(yīng)者發(fā)送事件處理的消息,把相應(yīng)的事件傳遞過去,第一響應(yīng)者開始檢測自身是否可以處理事件,若不能處理事件,就向下一個響應(yīng)者發(fā)送事件處理的消息,并把事件傳遞過去,若能處理事件,則處理事件并結(jié)束本次觸摸事件,重復(fù)這一過程,直到有響者可以處理事件或放棄本次觸摸事件。
當(dāng)你去研究觸摸事件的整個過程時,可能需要花上一些時間才能了解整個過程,才會發(fā)現(xiàn)整個過程分為兩步,才大概了解到每一步的一些關(guān)鍵細(xì)節(jié)。當(dāng)你了解了整個過程之后,可能又會產(chǎn)生一些疑問,為什么查找第一響應(yīng)者和查找可以處理事件的響應(yīng)者的過程是這樣?整個過程看上去很繁瑣,這樣處理方式有什么好處?
OK!我們現(xiàn)在換一種方式,站在設(shè)計模式的角度來看觸摸事件的整個過程(不會展開講每一個模式的細(xì)節(jié),只講述正式定義,詳細(xì)內(nèi)容會在設(shè)計模式的篇章里講述)。
整個過程的第一步是查找第一響應(yīng)者,查找第一響應(yīng)者利用了視圖層次結(jié)構(gòu)來完成,看到視圖層次結(jié)構(gòu)我們自然想到的是組合模式(Composite Pattern),大部分系統(tǒng)都是以組合模式來實現(xiàn)視圖層次結(jié)構(gòu)的,iOS也不例外。組合模式的正式定義:將對象組合成樹形結(jié)構(gòu)來表現(xiàn)“整體/部分”層次結(jié)構(gòu)(例如,視圖層次結(jié)構(gòu)),Composite使得用戶對單個對象和組合對象的使用具有一致性。組合模式使得Application對象不知道也不關(guān)心當(dāng)前的UIWindow對象是單個對象還是組合對象(Window是否有子視圖),這對于Application對象來說是透明的,以UIWindow對象為最高層組件的對象結(jié)構(gòu)(視圖層次結(jié)構(gòu))可以在程序運(yùn)行時動態(tài)改變,但這些改變并不會響影Application的代碼,因為Application對象對UIWindow對象的所有假定都基于UIWindow抽象的概念接口,而這些假定或者說Application對象對UIWindow對象的操作在編譯時就已經(jīng)固定下來。這樣的設(shè)計使得Application對象與UIWindow對象既可以保持穩(wěn)定的交互,又可以在程序運(yùn)行時動態(tài)改變以UIWindow對象為最高層組件的對象結(jié)構(gòu),既保證了穩(wěn)定性又提供了靈活性。所以,當(dāng)Application對象向UIWindow對象發(fā)送hitTest消息時,UIWindow對象根據(jù)運(yùn)行時的對象結(jié)構(gòu)完成相應(yīng)操作(遞歸結(jié)構(gòu))。


提供對象結(jié)構(gòu)很重要,因為它展示了對象之間如何協(xié)作,而提供類結(jié)構(gòu)同樣很重要,對象結(jié)構(gòu)中的每一個對象都是某一個類的實例,客戶(Application對象)對服務(wù)器對象(UIWindow對象)作出的所有假定都是基于這個對象的類的抽象(類的接口)。
第二步是查找可以處理事件的響應(yīng)者,查找是順著響應(yīng)鏈(響應(yīng)鏈由一組響應(yīng)者組成)來完成的,iOS中響應(yīng)鏈?zhǔn)且月氊?zé)鏈模式(Chain Of Responsibility Pattern)來實現(xiàn)。職責(zé)鏈模式的正式定義:使多個對象都有機(jī)會處理請求,從而避免請求的發(fā)送者和接收者之間的耦合關(guān)系。將這些對象連成一條鏈,并沿著這條鏈傳遞請求,直到有一個對象處理它為止。第一步獲取到的是職責(zé)鏈(響應(yīng)鏈)中的第一個接收者(Handler,第一響應(yīng)者),客戶向第一個接收者發(fā)送事件處理的消息,消息沿著鏈轉(zhuǎn)發(fā),若當(dāng)前接收者不能處理事件,就將消息傳遞給下一個接收者或者叫隱式接收者(Successor,即響應(yīng)鏈中的nextResponder)。

在職責(zé)鏈模式中,既然沒有一個明確的接收者,那么消息就不能保證一定會被處理。若有接收者可以處理事件,則本次消息傳遞結(jié)束,若沒有接收者可以處理事件,則本次事件被放棄處理。與組合模式一樣,職責(zé)鏈模式同樣提供了穩(wěn)定性和靈活性。Application對象與第一響應(yīng)者(FirstResponder,即職責(zé)鏈中的Handler)的交互在編譯時已經(jīng)固定,而響應(yīng)鏈的對象結(jié)構(gòu)則在運(yùn)行時動態(tài)地改變。
觸摸事件的處理應(yīng)用了組合模式和職責(zé)鏈模式,接下來我們再看一個應(yīng)用職責(zé)鏈模式的例子,Objective-C的消息機(jī)制(假定讀者知道消息機(jī)制的流程)。

當(dāng)我們向一個對象發(fā)送消息時,通過這個對象的isa指針來獲取該對象所對應(yīng)的類對象(或元類對象),再通過配對類對象(或元類對象)方法列表中的SEL來獲取相應(yīng)的IMP,若在當(dāng)前類對象(或元類對象)的方法列表中可以找到與SEL配對的IMP,則返回相應(yīng)的IMP并結(jié)束本次消息的查找,若不能在當(dāng)前類對象(或元類對象)的方法列表中找到與SEL配對的IMP,則把查找消息傳遞給SuperClass,從SuperClass的方法列表中繼續(xù)查找與SEL配對的IMP,重復(fù)這一過程,直到有類對象(或元類對象)處理消息或最終拋出doesNotRecognizeSelector異常。
職責(zé)鏈?zhǔn)歉鶕?jù)類結(jié)構(gòu)的繼承關(guān)系在運(yùn)行時動態(tài)決定的,或者說職責(zé)鏈?zhǔn)穷悓ο蠛驮悓ο笏鶚?gòu)成的對象結(jié)構(gòu)(Objective-C作為一門原型語言,類和元類本身也是對象),Handler就是當(dāng)前對象所對應(yīng)的類對象(或元類對象),Successor就是SuperClass,沿著這條職責(zé)鏈來查找消息。為什么說Objective-C是動態(tài)語言?Objective-C的動態(tài)性體現(xiàn)在很多方面,用鏈表來實現(xiàn)方法列表使得可以在運(yùn)行時動態(tài)添加方法,用isa指針指向的類對象(或元類對象)使得可以在運(yùn)行時改變指向(KVO,為什么KVO的實現(xiàn)是創(chuàng)建當(dāng)前類的子類來實現(xiàn)監(jiān)聽而不是用hook的方式?因為直接hook會影響其它同類對象的操作)。用職責(zé)鏈模式實現(xiàn)消息查找路徑使得可以在運(yùn)行時動態(tài)地改變路徑的對象結(jié)構(gòu),也為最終因找不到方法而進(jìn)行消息轉(zhuǎn)發(fā)提供了基礎(chǔ)。
例子不再一一列舉,留給有興趣的讀者自己去研究。無論是系統(tǒng)框架還是優(yōu)秀的第三方庫,或多或少都應(yīng)用了設(shè)計模式來實現(xiàn),雖然每個模式在應(yīng)用時都會根據(jù)實際情況作出一定的調(diào)整或變種,但在結(jié)構(gòu)上,所需要注要和考慮的細(xì)節(jié)上基本是一致的,當(dāng)你對這些模式有所了解時,就可以幫助你更好地理解你所研究的框架或架構(gòu),就可以使你更快地知道哪些是要點(diǎn),哪些是重點(diǎn)。這是對于你研究系統(tǒng)框架或優(yōu)秀的第三方庫時的幫助,除此以外,學(xué)習(xí)這些設(shè)計模式還為你的思考提供了另一個方向,提升了你的編程思想和程序的穩(wěn)定性,可擴(kuò)展性等。應(yīng)用設(shè)計模式有很多優(yōu)點(diǎn),但同時也存在著一些缺點(diǎn)。一般情況下,你所做的基本抽象都是從問題域中來的,當(dāng)你完成問題分析開始設(shè)計程序時,常常會在這些基本抽象或關(guān)鍵抽象上進(jìn)行更高層的抽象,會創(chuàng)建新的類和對象來協(xié)作,這些額外的類和對象會增加程序設(shè)計的復(fù)雜度,還會降低效率等,而且當(dāng)你的程序充滿了設(shè)計模式時,你還將面臨著另一個問題,是否過度設(shè)計?
除了設(shè)計模式,還有一些設(shè)計原則可以你幫助解決設(shè)計上的問題,但需要注意的是,設(shè)計原則只是指引,并非一定要照著來做,而且在實際開發(fā)當(dāng)中也不太可能這樣做(至少國內(nèi)的IT公司好像大部分是這樣子的)。例如單一功能原則,它規(guī)定一個類應(yīng)該只有一個發(fā)生變化的原因,但這個原則會大大增加你的程序設(shè)計復(fù)雜度。細(xì)化對象的粒度是一個分類過程,應(yīng)該通過多次迭代來完成,而不是一開始就花大量的時間去分析與設(shè)計(通常情況下你都沒有時間去做設(shè)計和考慮),這跟做性能優(yōu)化一樣,避免對未來的發(fā)展做過多的預(yù)測,除非你所面對的模塊有很明確的戰(zhàn)略目標(biāo)(扯蛋吧,在開發(fā)過程中不來改需求已經(jīng)很慶幸了),或者你所在的團(tuán)隊有這些明確的規(guī)定。
學(xué)習(xí)完設(shè)計模式后要面臨的最大問題是,怎么在實際開發(fā)中應(yīng)用,相信很多人都有這個的疑問,為什么面對實際問題時會無從下手?其實不管你是否有學(xué)習(xí)過設(shè)計模式,在你的日常開發(fā)當(dāng)中總會用到一個模式-策略模式(Strategy Pattern)。策略模式的正式定義:定義一系列的算法,把它們一個個封裝起來,并且使它們可相互替換。本模式使得算法可獨(dú)立于使用它的客戶而變化??吹竭@個定義你想到了什么?舉個例子,UICollectionView根據(jù)你所傳遞的UICollectionViewLayout進(jìn)行布局,通過改變UICollectionViewLayout來改變布局,UICollectionViewLayout就是模式中的Strategy,UICollectionView就是模式中的Context。再看一個更常見的例子,代理回調(diào),代理回調(diào)中的協(xié)議就是Strategy的接口,引入?yún)f(xié)議的類就是Strategy,而被代理的對象就是Context(例如,TableView還是那個TableView,但TableView的協(xié)議每次都有不同的實現(xiàn),TableView根據(jù)所提供的協(xié)議實現(xiàn)來展示最終的效果)。
回到怎么應(yīng)用這個問題,我個人的看法是,很多人在學(xué)設(shè)計模式時忽略了設(shè)計模式的基礎(chǔ)是面向?qū)ο螅珿OF23對設(shè)計模式的定義是,對被用來在特定情景下解決一般設(shè)計問題的類和相互通信的對象的描述。如果你本身還不知道怎么用面向?qū)ο髞矸治龊头纸鈫栴},還不清楚什么是對象模型,什么是類,什么是對象,怎么分類,你連基本的切入點(diǎn)和依據(jù)都沒有,你怎么可能知道在實際開發(fā)中該如何應(yīng)用設(shè)計模式,即使真的應(yīng)用了,也只是套了個結(jié)構(gòu)而已。
面向?qū)ο蟮膯栴},在此不展開討論,因為這些知識或者說這些關(guān)于編程思想上的東西,并不是一兩篇技術(shù)文就可以講述清楚,就可以傳達(dá)給你的,如果讀者有興趣,可以看一下《面向?qū)ο蠓治雠c設(shè)計》這本書,不管你目前是什么水平,或許你都可以在這本書里有所收獲。如果讀者想學(xué)設(shè)計模式,建議選Head First《設(shè)計模式》作為入門,GOF23作為拓展,《設(shè)計模式沉思錄》作為應(yīng)用后的思考,雖然我也很想支持國貨,但真的不建議選《大話設(shè)計》作為入門(雖然銷量很高),因為跟Head First《設(shè)計模式》相比,《大話設(shè)計》傳遞的是思考什么(相當(dāng)于給你指了一條路,然后告訴你沿著這條路走就是了,這樣就固化了你思想),而Head First《設(shè)計模式》傳遞的是如何思考(相當(dāng)于告訴你什么是路,怎么區(qū)分路,根據(jù)你自己的想法來選擇走什么路,這就給了你創(chuàng)造的空間),這在思維引導(dǎo)上差得真的不是一點(diǎn)半點(diǎn)。
個人感覺做程序設(shè)計要比做性能優(yōu)化更看團(tuán)隊的情況,因為你需要考慮團(tuán)隊成員的水平,怎么協(xié)作,面對有限的開發(fā)時間怎么分配任務(wù),怎么折中選擇實現(xiàn)等,有時候會有些有心無力的感覺。
最后
如果你看到這里還沒開噴,我也只能說聲謝謝??吹竭@里你可能想說,文中的內(nèi)容講得太籠統(tǒng)了,這本來就不是專門針對某一塊技術(shù)來寫的帖。
最后想說的是,沒有葵花寶典(即使有也不練啊??),也不要想著有人出來教你一套獨(dú)孤九劍。如果你想學(xué)習(xí)某一塊技術(shù),建議找相關(guān)的專題書來看,因為專題書所帶給你的全面性和專業(yè)性,不是網(wǎng)上一兩篇文章能給的。建議少用Objective-C的Runtime,多思考怎么從設(shè)計上解決當(dāng)前遇到的問題。建議不要片面去追求底層技術(shù)的待刨根問底(我們要有刨根問底的精神,但不要片面追求),除非你真的很有興趣和公司需要你這么發(fā)展,不然還是全面一點(diǎn)發(fā)展好,因為就像《面向?qū)ο蠓治雠c設(shè)計》這本書里面講的,有很多程序員都是拿著一門面向?qū)ο蟮恼Z言當(dāng)作面向過程的語言來使用。
