1 NSOperationQueue和GCD的區(qū)別是什么
GCD(Grand Central Dispatch)是底層的C語(yǔ)言構(gòu)成的API,而NSOperationQueue及相關(guān)對(duì)象是Objc的對(duì)象。在GCD中,在隊(duì)列中執(zhí)行的是由block構(gòu)成的任務(wù),這是一個(gè)輕量級(jí)的數(shù)據(jù)結(jié)構(gòu);NSOperation是一個(gè)抽象類,它封裝了線程的細(xì)節(jié)實(shí)現(xiàn),我們可以通過子類化該對(duì)象,加上NSQueue來同面向?qū)ο蟮乃季S,管理多線程程序。而Operation為我們提供了更多的選擇;
2.在NSOperationQueue中,我們可以隨時(shí)取消已經(jīng)設(shè)定要準(zhǔn)備執(zhí)行的任務(wù)(當(dāng)然,已經(jīng)開始的任務(wù)就無法阻止了),而GCD沒法停止已經(jīng)加入queue的block(其實(shí)是有的,但需要許多復(fù)雜的代碼);
3.NSOperation能夠方便地設(shè)置依賴關(guān)系,我們可以讓一個(gè)Operation依賴于另一個(gè)Operation,這樣的話盡管兩個(gè)Operation處于同一個(gè)并行隊(duì)列中,但前者會(huì)直到后者執(zhí)行完畢后再執(zhí)行;
4.我們能將KVO應(yīng)用在NSOperation中,可以監(jiān)聽一個(gè)Operation是否完成或取消,這樣子能比GCD更加有效地掌控我們執(zhí)行的后臺(tái)任務(wù);
5.在NSOperation中,我們能夠設(shè)置NSOperation的priority優(yōu)先級(jí),能夠使同一個(gè)并行隊(duì)列中的任務(wù)區(qū)分先后地執(zhí)行,而在GCD中,我們只能區(qū)分不同任務(wù)隊(duì)列的優(yōu)先級(jí),如果要區(qū)分block任務(wù)的優(yōu)先級(jí),也需要大量的復(fù)雜代碼;
6.我們能夠?qū)SOperation進(jìn)行繼承,在這之上添加成員變量與成員方法,提高整個(gè)代碼的復(fù)用度,這比簡(jiǎn)單地將block任務(wù)排入執(zhí)行隊(duì)列更有自由度,能夠在其之上添加更多自定制的功能。
7.GCD?是嚴(yán)格的隊(duì)列,先進(jìn)先出FIFO;NSOperation可以改動(dòng)?優(yōu)先級(jí)(或者說服務(wù)質(zhì)量)改變執(zhí)行順序
8.NSOperation的高級(jí):最大并發(fā)數(shù),控制線程個(gè)數(shù),優(yōu)化了線程的暫停、繼續(xù)、取消功能(GCD實(shí)現(xiàn)起來太難,可以用?KVO?),依賴關(guān)系,可以讓異步任務(wù)
同步執(zhí)行.
2、GCD與NSThread的區(qū)別: ?
1).?NSThread?通過?@selector?指定要執(zhí)行的方法,代碼分散,?依靠的是NSObject的分類實(shí)現(xiàn)的線程之間的通訊,如果要開線程必須創(chuàng)建多個(gè)線程對(duì)象。經(jīng)常只用的是[NSTread current]?查看當(dāng)前的線程。
NSThread是一個(gè)控制線程執(zhí)行的對(duì)象,它不如NSOperation抽象,通過它我們可以方便的得到一個(gè)線程,并控制它。但NSThread的線程之間的并發(fā)控制,是需要我們自己來控制的,可以通過NSCondition實(shí)現(xiàn)。
?2).GCD 通過?block指定要執(zhí)行的代碼,代碼集中, 所有的代碼寫在一起的,讓代碼更加簡(jiǎn)單,易于閱讀和維護(hù),不需要管理線程的創(chuàng)建/銷毀/復(fù)用的過程!程序員不用關(guān)心線程的生命周期
3?利用NSOperation與NSOperationqueue處理多線程時(shí),有3個(gè)NSOperation分別為A,B,C,要求A,B執(zhí)行完畢后,在執(zhí)行C,如何做?
有三種實(shí)現(xiàn)方案
方案一:串行隊(duì)列同步執(zhí)行(GCD實(shí)現(xiàn))
A,B放在串行隊(duì)列中執(zhí)行,執(zhí)行完畢,主隊(duì)列執(zhí)行任務(wù)C
方案二:隊(duì)列依賴實(shí)現(xiàn)
方案三:并發(fā)隊(duì)列和主隊(duì)列實(shí)現(xiàn)
http://www.itdecent.cn/p/0b0d9b1f1f19
多線程
一.資源搶奪
2>?資源搶奪解決方案
@sychronized{ }
dispatch_barrier_async
NSLock NSCondition
dispatch_semaphore_wait
4+(void)load與+(void)initialize區(qū)別
?load方法:
?1.當(dāng)類加載到OC運(yùn)行時(shí)環(huán)境(內(nèi)存)中的時(shí)候,就會(huì)調(diào)用一次(一個(gè)類只會(huì)加載一次).
?2. 程序一啟動(dòng)就會(huì)調(diào)用.
?3.程序運(yùn)行過程中,只會(huì)調(diào)用1次.
?initialize方法:
?1.當(dāng)?shù)谝淮问褂眠@個(gè)類的時(shí)候(比如調(diào)用了類的某個(gè)方法)才會(huì)調(diào)用.
?2. 并非程序一啟動(dòng)就會(huì)調(diào)用.
load和initialize的共同特點(diǎn)
在不考慮開發(fā)者主動(dòng)使用的情況下,系統(tǒng)最多會(huì)調(diào)用一次
?如果父類和子類都被調(diào)用,父類的調(diào)用一定在子類之前
都是為了應(yīng)用運(yùn)行提前創(chuàng)建合適的運(yùn)行環(huán)境
?在使用時(shí)都不要過重地依賴于這兩個(gè)方法,除非真正必要
?它們的相同點(diǎn)在于:方法只會(huì)被調(diào)用一次。(其實(shí)這是相對(duì)runtime來說的,后邊會(huì)做進(jìn)一步解釋)。
??load是只要類所在文件被引用就會(huì)被調(diào)用,而initialize是在類或者其子類的第一個(gè)方法被調(diào)用前調(diào)用。所以如果類沒有被引用進(jìn)項(xiàng)目,就不會(huì)有l(wèi)oad調(diào)用;但即使類文件被引用進(jìn)來,但是沒有使用,那么initialize也不會(huì)被調(diào)用。
?文檔也明確闡述了方法調(diào)用的順序:父類(Superclass)的方法優(yōu)先于子類(Subclass)的方法,類中的方法優(yōu)先于類別(Category)中的方法。
5?說說關(guān)于UDP/TCP的區(qū)別?
UDP(User Data Protocol,用戶數(shù)據(jù)報(bào)協(xié)議)是與TCP相對(duì)應(yīng)的協(xié)議。它是面向非連接的協(xié)議,它不與對(duì)方建立連接,而是直接就把數(shù)據(jù)包發(fā)送過去! UDP適用于一次只傳送少量數(shù)據(jù)、對(duì)可靠性要求不高的應(yīng)用環(huán)境。
1.只管發(fā)送,不確認(rèn)對(duì)方是否接收到
2.將數(shù)據(jù)及源和目的封裝成數(shù)據(jù)包中,不需要建立連接
3.每個(gè)數(shù)據(jù)報(bào)的大小限制在64K之內(nèi)
4.因?yàn)闊o需連接,因此是不可靠協(xié)議
5.不需要建立連接,速度快
應(yīng)用場(chǎng)景:多媒體教室/網(wǎng)絡(luò)流媒體
TCP(Transmission Control Protocol,傳輸控制協(xié)議)是基于連接的協(xié)議,也就是說,在正式收發(fā)數(shù)據(jù)前,必須和對(duì)方建立可靠的連接。一個(gè)TCP連接必須要經(jīng)過三次“對(duì)話”才能 建立起來,其中的過程非常復(fù)雜,我們這里只做簡(jiǎn)單、形象的介紹,你只要做到能夠理解這個(gè)過程即可。
1.建立連接,形成傳輸數(shù)據(jù)的通道
2.在連接中進(jìn)行大數(shù)據(jù)傳輸(數(shù)據(jù)大小不收限制)
3.通過三次握手完成連接,是可靠協(xié)議,安全送達(dá)
4.必須建立連接,效率會(huì)稍低
TCP傳輸控制協(xié)議主要包含下列任務(wù)和功能:
*?確保IP數(shù)據(jù)報(bào)的成功傳遞。
*?對(duì)程序發(fā)送的大塊數(shù)據(jù)進(jìn)行分段和重組。
*?確保正確排序及按順序傳遞分段的數(shù)據(jù)。
*?通過計(jì)算校驗(yàn)和,進(jìn)行傳輸數(shù)據(jù)的完整性檢查。
簡(jiǎn)單的說,TCP注重?cái)?shù)據(jù)安全,而UDP數(shù)據(jù)傳輸快點(diǎn),但安全性一般
3> TCP與UDP的區(qū)別:
3.1>基于連接與無連接;
3.2>對(duì)系統(tǒng)資源的要求(TCP較多,UDP少);
3.3>UDP程序結(jié)構(gòu)較簡(jiǎn)單;
3.4>流模式與數(shù)據(jù)報(bào)模式;
3.5>TCP保證數(shù)據(jù)正確性,UDP可能丟包,TCP保證數(shù)據(jù)順序,UDP不保證
6?長(zhǎng)鏈接&短鏈接
?TCP短連接
?我們模擬一下TCP短連接的情況,client向server發(fā)起連接請(qǐng)求,server接到請(qǐng)求,然后雙方建立連接。client向server?發(fā)送消息,server回應(yīng)client,然后一次讀寫就完成了,這時(shí)候雙方任何一個(gè)都可以發(fā)起close操作,不過一般都是client先發(fā)起?close操作。為什么呢,一般的server不會(huì)回復(fù)完client后立即關(guān)閉連接的,當(dāng)然不排除有特殊的情況。從上面的描述看,短連接一般只會(huì)在 client/server間傳遞一次讀寫操作
?TCP長(zhǎng)連接
?接下來我們?cè)倌M一下長(zhǎng)連接的情況,client向server發(fā)起連接,server接受client連接,雙方建立連接。Client與server完成一次讀寫之后,它們之間的連接并不會(huì)主動(dòng)關(guān)閉,后續(xù)的讀寫操作會(huì)繼續(xù)使用這個(gè)連接。
?長(zhǎng)連接短連接操作過程
短連接的操作步驟是:
建立連接——數(shù)據(jù)傳輸——關(guān)閉連接…建立連接——數(shù)據(jù)傳輸——關(guān)閉連接
?長(zhǎng)連接的操作步驟是:
??建立連接——數(shù)據(jù)傳輸…(保持連接)…數(shù)據(jù)傳輸——關(guān)閉連接
?什么時(shí)候用長(zhǎng)連接,短連接?
?長(zhǎng)連接多用于操作頻繁,點(diǎn)對(duì)點(diǎn)的通訊,而且連接數(shù)不能太多情況,。每個(gè)TCP連接都需要三步握手,這需要時(shí)間,如果每個(gè)操作都是先連接,再操作的話 那么處理速度會(huì)降低很多,所以每個(gè)操作完后都不斷開,次處理時(shí)直接發(fā)送數(shù)據(jù)包就OK了,不用建立TCP連接。例如:數(shù)據(jù)庫(kù)的連接用長(zhǎng)連接, 如果用短連接頻繁的通信會(huì)造成socket錯(cuò)誤,而且頻繁的socket?創(chuàng)建也是對(duì)資源的浪費(fèi)。
長(zhǎng)連接和短連接的優(yōu)點(diǎn)和缺點(diǎn)
?由上可以看出,長(zhǎng)連接可以省去較多的TCP建立和關(guān)閉的操作,減少浪費(fèi),節(jié)約時(shí)間。對(duì)于頻繁請(qǐng)求資源的客戶來說,較適用長(zhǎng)連接。不過這里存在一個(gè)問 題,存活功能的探測(cè)周期太長(zhǎng),還有就是它只是探測(cè)TCP連接的存活,屬于比較斯文的做法,遇到惡意的連接時(shí),保活功能就不夠使了。在長(zhǎng)連接的應(yīng)用場(chǎng)景 下,client端一般不會(huì)主動(dòng)關(guān)閉它們之間的連接,Client與server之間的連接如果一直不關(guān)閉的話,會(huì)存在一個(gè)問題,隨著客戶端連接越來越 多,server早晚有扛不住的時(shí)候,這時(shí)候server端需要采取一些策略,如關(guān)閉一些長(zhǎng)時(shí)間沒有讀寫事件發(fā)生的連接,這樣可 以避免一些惡意連接導(dǎo)致server端服務(wù)受損;如果條件再允許就可以以客戶端機(jī)器為顆粒度,限制每個(gè)客戶端的最大長(zhǎng)連接數(shù),這樣可以完全避免某個(gè)蛋疼的 客戶端連累后端服務(wù)。
?短連接對(duì)于服務(wù)器來說管理較為簡(jiǎn)單,存在的連接都是有用的連接,不需要額外的控制手段。但如果客戶請(qǐng)求頻繁,將在TCP的建立和關(guān)閉操作上浪費(fèi)時(shí)間和帶寬。
長(zhǎng)連接和短連接的產(chǎn)生在于client和server采取的關(guān)閉策略,具體的應(yīng)用場(chǎng)景采用具體的策略,沒有十全十美的選擇,只有合適的選擇。
7 TCP?傳輸原理
1>TCP如何防止亂序和丟包
???TCP數(shù)據(jù)包的頭格式中有兩個(gè)概念,Sequence Number是數(shù)據(jù)包的序號(hào),用來解決網(wǎng)絡(luò)包亂序(reordering)問題。Acknowledgement Number就是ACK——用于確認(rèn)收到,用來解決不丟包的問題。
???位碼即tcp標(biāo)志位,有6種標(biāo)示:SYN(synchronous建立聯(lián)機(jī))?ACK(acknowledgement?確認(rèn)) PSH(push傳送) FIN(finish結(jié)束) RST(reset重置) URG(urgent緊急)Sequence number(順序號(hào)碼) Acknowledge number(確認(rèn)號(hào)碼).
??SeqNum的增加是和傳輸?shù)淖止?jié)數(shù)相關(guān)的,TCP傳輸數(shù)據(jù)時(shí),A主機(jī)第一次傳輸1440個(gè)字節(jié),seq=1,那么第二次時(shí)seq = 1441,B拼接數(shù)據(jù)就是根據(jù)seq進(jìn)行拼接的,seq數(shù)字不斷累加避免了亂序.B主機(jī)收到第一次數(shù)據(jù)包以后會(huì)返回ack = 1441.
???A主機(jī)收到B的ack = 1441時(shí),就知道第一個(gè)數(shù)據(jù)包B已收到.?如果B沒有收到第一次的數(shù)據(jù)包,那么B再收到A的數(shù)據(jù)包時(shí),他就會(huì)發(fā)ack = 1回去,A收到B的回復(fù),發(fā)現(xiàn)B沒有收到第一次數(shù)據(jù)包,就會(huì)重發(fā)第一次數(shù)據(jù)包,這樣就可以防止丟包.
2>描述一下三次握手
??第一次握手:建立連接時(shí),客戶端發(fā)送syn包(syn=j)到服務(wù)器,并進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器確認(rèn);
??第二次握手:服務(wù)器收到syn包,必須確認(rèn)客戶的SYN(ack=j+1),同時(shí)自己也發(fā)送一個(gè)SYN包(syn=k),即SYN+ACK包,此時(shí)服務(wù)器進(jìn)入SYN_RECV狀態(tài);
??第三次握手:客戶端收到服務(wù)器的SYN+ACK包,向服務(wù)器發(fā)送確認(rèn)包ACK(ack=k+1),此包發(fā)送完畢,客戶端和服務(wù)器進(jìn)入ESTABLISHED狀態(tài),完成三次握手。完成三次握手,客戶端與服務(wù)器開始傳送數(shù)據(jù).
3>三次握手實(shí)現(xiàn)的過程:
第一次握手:建立連接時(shí),客戶端發(fā)送同步序列編號(hào)到服務(wù)器,并進(jìn)入發(fā)送狀態(tài),等待服務(wù)器確認(rèn)
第二次:服務(wù)器收到同步序列編號(hào),并確認(rèn)同時(shí)自己也發(fā)送一個(gè)同步序列編號(hào)+確認(rèn)標(biāo)志,此時(shí)服務(wù)器進(jìn)入接收狀態(tài)
第三次:客戶端收到服務(wù)器發(fā)送的包,并向服務(wù)器發(fā)送確認(rèn)標(biāo)志,隨后鏈接成功。
注意:是在鏈接成功后在進(jìn)行數(shù)據(jù)傳輸。
8?.http和scoket通信的區(qū)別?socket連接相關(guān)庫(kù),TCP,UDP的連接方法,HTTP的幾種常用方式?
http和scoket通信的區(qū)別:
http是客戶端用http協(xié)議進(jìn)行請(qǐng)求,發(fā)送請(qǐng)求時(shí)候需要封裝http請(qǐng)求頭,并綁定請(qǐng)求的數(shù)據(jù),服務(wù)器一般有web服務(wù)器配合(當(dāng)然也非絕對(duì))。?http請(qǐng)求方式為客戶端主動(dòng)發(fā)起請(qǐng)求,服務(wù)器才能給響應(yīng),一次請(qǐng)求完畢后則斷開連接,以節(jié)省資源。服務(wù)器不能主動(dòng)給客戶端響應(yīng)(除非采取http長(zhǎng)連接技術(shù))。iphone主要使用類是NSUrlSe on。?
scoket是客戶端跟服務(wù)器直接使用socket“套接字”進(jìn)行連接,并沒有規(guī)定連接后斷開,所以客戶端和服務(wù)器可以保持連接通道,雙方都可以主動(dòng)發(fā)送數(shù)據(jù)。一般在游戲開發(fā)或股票開發(fā)這種要求即時(shí)性很強(qiáng)并且保持發(fā)送數(shù)據(jù)量比較大的場(chǎng)合使用。主要使用類是CFSocketRef。
9?BLOCK
使用block時(shí)什么情況會(huì)發(fā)生引用循環(huán),如何解決?
一個(gè)對(duì)象中強(qiáng)引用了block,在block中又使用了該對(duì)象,就會(huì)發(fā)射循環(huán)引用。 解決方法是將該對(duì)象使用__weak或者_(dá)_block修飾符修飾之后再在block中使用。
id weak weakSelf = self;?或者weak __typeof(&*self)weakSelf = self該方法可以設(shè)置宏
id __block weakSelf = self;
事后解除指控
10?ivar、getter、setter 是如何生成并添加到這個(gè)類中的?
“自動(dòng)合成”( autosynthesis)
完成屬性定義后,編譯器會(huì)自動(dòng)編寫訪問這些屬性所需的方法,此過程叫做“自動(dòng)合成”(autosynthesis)。需要強(qiáng)調(diào)的是,這個(gè)過程由編譯 器在編譯期執(zhí)行,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼。除了生成方法代碼?getter、setter 之外,編譯器還要自動(dòng)向類中添加適當(dāng)類型的實(shí)例變量,并且在屬性名前面加下劃線,以此作為實(shí)例變量的名字。在前例中,會(huì)生成兩個(gè)實(shí)例變量,其名稱分別為_firstName與?_lastName。也可以在類的實(shí)現(xiàn)代碼里通過?@synthesize?語(yǔ)法來指定實(shí)例變量的名字.
11?@protocol和?category?中如何使用@property
在?protocol?中使用?property?只會(huì)生成setter 和?getter?方法聲明,我們使用屬性的目的,是希望遵守我協(xié)議的對(duì)象能實(shí)現(xiàn)該屬性
category使用?@property?也是只會(huì)生成 setter 和?getter?方法的聲明,如果我們真的需要給?category?增加屬性的實(shí)現(xiàn),需要借助于運(yùn)行時(shí)的兩個(gè)函數(shù):
objc_setAssociatedObject
objc_getAssociatedObject
12?.runtime如何通過selector找到對(duì)應(yīng)的IMP地址?
每一個(gè)類對(duì)象中都一個(gè)方法列表(isa),方法列表中記錄著方法的名稱,方法實(shí)現(xiàn),以及參數(shù)類型,其實(shí)selector本質(zhì)就是方法名稱,通過這個(gè)方法名稱就可以在方法列表中找到對(duì)應(yīng)的方法實(shí)現(xiàn).
13.TableView性能優(yōu)化
1.行高一定要緩存!
2.不要?jiǎng)討B(tài)創(chuàng)建子視圖
所有的子視圖都預(yù)先創(chuàng)建,如果不需要顯示可以設(shè)置?hidden
3.所有的子視圖都應(yīng)該添加到?contentView?上
4.所有的子視圖都必須指定背景顏色,且所有的顏色都不要使用 alpha
5.cell?柵格化
6.異步繪制
UITableView的優(yōu)化主要從三個(gè)方面入手:
1.提前計(jì)算并緩存好高度(布局),因?yàn)閔eightForRowAtIndexPath:是調(diào)用最頻繁的方法;
2.異步繪制,遇到復(fù)雜界面,遇到性能瓶頸時(shí),可能就是突破口;
3.滑動(dòng)時(shí)按需加載,這個(gè)在大量圖片展示,網(wǎng)絡(luò)加載的時(shí)候很管用?。⊿DWebImage已經(jīng)實(shí)現(xiàn)異步加載,配合這條性能杠杠的)。
正確使用reuseIdentifier來重用Cells
盡量使所有的view opaque,包括Cell自身
盡量少用或不用透明圖層
如果Cell內(nèi)現(xiàn)實(shí)的內(nèi)容來自web,使用異步加載,緩存請(qǐng)求結(jié)果
減少subviews的數(shù)量
在heightForRowAtIndexPath:中盡量不使用cellForRowAtIndexPath:,如果你需要用到它,只用一次然后緩存結(jié)果
盡量少用addView給Cell動(dòng)態(tài)添加View,可以初始化時(shí)就添加,然后通過hide來控制是否顯示
?在使用UITableView的時(shí)候,有的時(shí)候你會(huì)碰到Cell卡頓,圖片加載慢,使得滑動(dòng)cell時(shí)變得不那么流暢,這些都會(huì)影響用戶體驗(yàn),拉低整體app的效果。
1.???使用cell重用機(jī)制,盡可能快地返回重用cell實(shí)例? ?這點(diǎn)大家都應(yīng)該比較清楚,使用reuse機(jī)制能大幅降低創(chuàng)建cell所帶來的損耗,這就要各位在UITableView的dataSource中實(shí)現(xiàn)的tableView:cellForRowAtIndexPath:方法。只是有一點(diǎn),盡量不要在此時(shí)綁定數(shù)據(jù),因?yàn)槟壳霸谄聊簧线€沒有cell,可以在UITableView的delegate方法tableView:willDisplayCell:forRowAtIndexPath:中進(jìn)行數(shù)據(jù)的填充。 ? ? 需要說明的是,你可能會(huì)動(dòng)態(tài)計(jì)算cell高度,但最好是不要選擇Autolayout。使用Autolayout后,會(huì)根據(jù)cell的子視圖使得求解的約束也越多,從而降低計(jì)算速度,影響滑動(dòng)時(shí)FPS。所以,為了使tableview平滑滾動(dòng),請(qǐng)使用動(dòng)態(tài)計(jì)算高度,不要選擇Autolayout。
2. cell的subViews的各級(jí)opaque值要設(shè)成YES
?opaque用于輔助繪圖系統(tǒng),表示UIView是否透明。在不透明的情況下,渲染視圖時(shí)需要快速地渲染,以提高性能。渲染最慢的操作之一是混合(blending)。提高性能的方法是減少混合操作的次數(shù),其實(shí)就是GPU的不合理使用,這是硬件來完成的(混合操作由GPU來執(zhí)行,因?yàn)檫@個(gè)硬件就是用來做混合操作的,當(dāng)然不只是混合)。 優(yōu)化混合操作的關(guān)鍵點(diǎn)是在平衡CPU和GPU的負(fù)載。??
? ? ? ?還有就是cell的layer的shouldRasterize要設(shè)成YES。
3. 在繪制字符串時(shí),盡可能使用drawAtPoint:withFont: ,?在繪制圖片,盡量使用drawAtPoint
?? ?不要使用更復(fù)雜的drawAtPoint:(CGPoint)point forWidth:(CGFloat)width withFont:(UIFont*)font lineBreakMode:(UILineBreakMode)lineBreakMode;如果要繪制過長(zhǎng)的字符串,建議先截?cái)?,然后使用drawAtPoint:withFont:方法繪制。
??不要使用drawInRect, 因?yàn)樗诶L制過程中對(duì)圖片放縮大小,消耗CPU。
? ? ? ?其實(shí),最快的繪制就是你不要做任何繪制。有時(shí),通過UIGraphicsBeginImageContextWithOptions()或者CGBitmapContextCeate()創(chuàng)建位圖會(huì)顯得更有意義,從位圖上面抓取圖像,并設(shè)置為?CALayer?的內(nèi)容。
如果你必須實(shí)現(xiàn)?-drawRect:,并且你必須繪制大量的東西,這將占用時(shí)間。
圖片的話,你可能會(huì)用位圖來替代:
5. cell異步加載圖片以及緩存
?對(duì)于cell里的圖片采用異步的方式,加載好后緩存。當(dāng)圖片還沒有請(qǐng)求加載時(shí),你可以使用默認(rèn)圖片。
?一旦你緩存好圖片,使用cell的重用機(jī)制時(shí)就可以從關(guān)聯(lián)好的視圖源里以相應(yīng)的url來找到對(duì)應(yīng)的緩存圖片,緩存大大節(jié)省重復(fù)請(qǐng)求圖片的耗損。只是你要考慮內(nèi)存級(jí)別的緩存還是磁盤級(jí)別的緩存,記得使用完畢清緩存哦!(記得減少內(nèi)存級(jí)別的拷貝)
為了防止圖片多次下載,我們需要對(duì)圖片做緩存,緩存分為內(nèi)存緩存于沙盒緩存,我們當(dāng)然兩種都要實(shí)現(xiàn)。
般情況下在我們會(huì)在cellForRow方法里面設(shè)置cell的圖片數(shù)據(jù)源,也就是說如果一個(gè)cell的imageview對(duì)象開啟了一個(gè)下載任務(wù),這個(gè)時(shí)候該cell對(duì)象發(fā)生了重用,新的image數(shù)據(jù)源會(huì)開啟另外的一個(gè)下載任務(wù),由于他們關(guān)聯(lián)的imageview對(duì)象實(shí)際上是同一個(gè)cell實(shí)例的imageview對(duì)象,就會(huì)發(fā)生2個(gè)下載任務(wù)回調(diào)給同一個(gè)imageview對(duì)象。這個(gè)時(shí)候就有必要做一些處理,避免回調(diào)發(fā)生時(shí),錯(cuò)誤的image數(shù)據(jù)源刷新了UI。
在我們向下滑動(dòng)tableview的時(shí)候我們需要手動(dòng)去取消掉下載操作,當(dāng)用戶停止滑動(dòng),再去執(zhí)行下載操作
如果快速滑下去,然后又滑回來的話,圖片是過了一會(huì)才顯示出來,這是因?yàn)榭焖倩瑒?dòng)的時(shí)候,舊數(shù)據(jù)源的下載任務(wù)被取消掉了。
異步下載圖片我們用的是NSOperation,并且創(chuàng)建一個(gè)全局的queue來管理下載圖片的操作。
在把圖片顯示到Cell上之前
先判斷內(nèi)存中(images字典中)有沒有圖片,
如果有,則取出url對(duì)應(yīng)的圖片來顯示,
如果沒有,再去沙盒緩存中查看,當(dāng)然存到沙盒中都是NSData。
如果沙盒緩存中有,我們?nèi)〕鰧?duì)應(yīng)的數(shù)據(jù)給Cell去顯示
如果沙盒中也沒有圖片,我們先顯示占位圖片。再創(chuàng)建operation去執(zhí)行下載操作了。
當(dāng)然在創(chuàng)建operation之前,我們要判斷這個(gè)operation操作是否存在
如果沒有下載操作,我們才需要真正的去創(chuàng)建operation執(zhí)行下載。
創(chuàng)建好下載操作之后應(yīng)該把該操作存放到全局隊(duì)列中去異步執(zhí)行,同時(shí)吧操作放入operations字典中記錄下來。
下載完成之后:
把下載好的圖片放到內(nèi)存中、同時(shí)存到沙盒緩存中
執(zhí)行完上面的操作之后回到主線程刷新表格,
從operations字典中移除下載操作(防止operations越來越大,同時(shí)保證下載失敗后,能重新下載)
2、cell高度的計(jì)算
這邊我們分為兩種cell,一種是定高的cell,另外一種是動(dòng)態(tài)高度的cell。
(1)定高的cell,應(yīng)該采用如下方式:
self.tableView.rowHeight = 88;
這個(gè)方法指定了所有cell高度都是88的tableview,rowHeight默認(rèn)的值是44,所以一個(gè)空的TableView會(huì)顯示成這個(gè)樣子。對(duì)于定高cell,直接采用上面方式給定高度,不需要實(shí)現(xiàn)tableView:heightForRowAtIndexPath:以節(jié)省不必要的計(jì)算和開銷。
(2)動(dòng)態(tài)高度的cell
我們需要實(shí)現(xiàn)它的代理,來給出高度:
-(CGFloat)tableView:(UITableView *)tableViewheightForRowAtIndexPath:(NSIndexPath *)indexPath{
// return xxx
}
這個(gè)代理方法實(shí)現(xiàn)后,上面的rowHeight的設(shè)置將會(huì)變成無效。在這個(gè)方法中,我們需要提高cell高度的計(jì)算效率,來節(jié)省時(shí)間。
自從iOS8之后有了self-sizing cell的概念,cell可以自己算出高度,使用self-sizing cell需要滿足以下三個(gè)條件:
(1)使用Autolayout進(jìn)行UI布局約束(要求cell.contentView的四條邊都與內(nèi)部元素有約束關(guān)系)。
(2)指定TableView的estimatedRowHeight屬性的默認(rèn)值。
(3)指定TableView的rowHeight屬性為UITableViewAutomaticDimension。
除了提高cell高度的計(jì)算效率之外,對(duì)于已經(jīng)計(jì)算出的高度,我們需要進(jìn)行緩存,對(duì)于已經(jīng)計(jì)算過的高度,沒有必要進(jìn)行計(jì)算第二次。 ?
3、渲染
為了保證TableView的流暢,當(dāng)快速滑動(dòng)的時(shí)候,cell必須被快速的渲染出來。所以cell渲染的速度必須快。如何提高cell的渲染速度呢?
(1)當(dāng)有圖像時(shí),預(yù)渲染圖像,在bitmap context先將其畫一遍,導(dǎo)出成UIImage對(duì)象,然后再繪制到屏幕,這會(huì)大大提高渲染速度。具體內(nèi)容可以自行查找“利用預(yù)渲染加速顯示iOS圖像”相關(guān)資料。
(2)渲染最好時(shí)的操作之一就是混合(blending)了,所以我們不要使用透明背景,將cell的opaque值設(shè)為Yes,背景色不要使用clearColor,盡量不要使用陰影漸變等
(3)由于混合操作是使用GPU來執(zhí)行,我們可以用CPU來渲染,這樣混合操作就不再執(zhí)行??梢栽赨IView的drawRect方法中自定義繪制。
4、減少視圖的數(shù)目
我們?cè)赾ell上添加系統(tǒng)控件的時(shí)候,實(shí)際上系統(tǒng)都會(huì)調(diào)用底層的接口進(jìn)行繪制,大量添加控件時(shí),會(huì)消耗很大的資源并且也會(huì)影響渲染的性能。當(dāng)使用默認(rèn)的UITableViewCell并且在它的ContentView上面添加控件時(shí)會(huì)相當(dāng)消耗性能。所以目前最佳的方法還是繼承UITableViewCell,并重寫drawRect方法。
5、減少多余的繪制操作
在實(shí)現(xiàn)drawRect方法的時(shí)候,它的參數(shù)rect就是我們需要繪制的區(qū)域,在rect范圍之外的區(qū)域我們不需要進(jìn)行繪制,否則會(huì)消耗相當(dāng)大的資源。
6、不要給cell動(dòng)態(tài)添加subView
在初始化cell的時(shí)候就將所有需要展示的添加完畢,然后根據(jù)需要來設(shè)置hide屬性顯示和隱藏。
7、異步化UI,不要阻塞主線程
我們時(shí)常會(huì)看到這樣一個(gè)現(xiàn)象,就是加載時(shí)整個(gè)頁(yè)面卡住不動(dòng),怎么點(diǎn)都沒用,仿佛死機(jī)了一般。原因是主線程被阻塞了。所以對(duì)于網(wǎng)路數(shù)據(jù)的請(qǐng)求或者圖片的加載,我們可以開啟多線程,將耗時(shí)操作放到子線程中進(jìn)行,異步化操作。這個(gè)或許每個(gè)iOS開發(fā)者都知道的知識(shí),不必多講。
8、滑動(dòng)時(shí)按需加載對(duì)應(yīng)的內(nèi)容
如果目標(biāo)行與當(dāng)前行相差超過指定行數(shù),只在目標(biāo)滾動(dòng)范圍的前后指定3行加載。
-(void)scrollViewWillEndDragging:(UIScrollView *)scrollViewwithVelocity:(CGPoint)velocitytargetContentOffset:(inoutCGPoint *)targetContentOffset{
NSIndexPath *ip=[selfindexPathForRowAtPoint:CGPointMake(0,targetContentOffset->y)];
NSIndexPath *cip=[[selfindexPathsForVisibleRows]firstObject];
NSIntegerskipCount=8;
if(labs(cip.row-ip.row)>skipCount){
NSArray *temp=[selfindexPathsForRowsInRect:CGRectMake(0,targetContentOffset->y,self.width,self.height)];
NSMutableArray *arr=[NSMutableArrayarrayWithArray:temp];
if(velocity.y<0){
NSIndexPath *indexPath=[templastObject];
if(indexPath.row+33){
[arraddObject:[NSIndexPathindexPathForRow:indexPath.row-3inSection:0]];
[arraddObject:[NSIndexPathindexPathForRow:indexPath.row-2inSection:0]];
[arraddObject:[NSIndexPathindexPathForRow:indexPath.row-1inSection:0]];
}
}
[needLoadArraddObjectsFromArray:arr];
}
}
記得在tableView:cellForRowAtIndexPath:方法中加入判斷:
1
2
3
4
if(needLoadArr.count>0&&[needLoadArrindexOfObject:indexPath]==NSNotFound){
????[cellclear];
????return;
}
滑動(dòng)很快時(shí),只加載目標(biāo)范圍內(nèi)的cell,這樣按需加載(配合SDWebImage),極大提高流暢度。
14?優(yōu)化方案
官方對(duì)離屏渲染產(chǎn)生性能問題也進(jìn)行了優(yōu)化:
iOS 9.0之前UIimageView跟UIButton設(shè)置圓角都會(huì)觸發(fā)離屏渲染。
iOS 9.0之后UIButton設(shè)置圓角會(huì)觸發(fā)離屏渲染,而UIImageView里png圖片設(shè)置圓角不會(huì)觸發(fā)離屏渲染了,如果設(shè)置其他陰影效果之類的還是會(huì)觸發(fā)離屏渲染的。
(1)圓角優(yōu)化
在APP開發(fā)中,圓角圖片還是經(jīng)常出現(xiàn)的。如果一個(gè)界面中只有少量圓角圖片或許對(duì)性能沒有非常大的影響,但是當(dāng)圓角圖片比較多的時(shí)候就會(huì)APP性能產(chǎn)生明顯的影響。
我們?cè)O(shè)置圓角一般通過如下方式:
?imageView.layer.cornerRadius=CGFloat(10);
imageView.layer.masksToBounds=YES;
這樣處理的渲染機(jī)制是GPU在當(dāng)前屏幕緩沖區(qū)外新開辟一個(gè)渲染緩沖區(qū)進(jìn)行工作,也就是離屏渲染,這會(huì)給我們帶來額外的性能損耗,如果這樣的圓角操作達(dá)到一定數(shù)量,會(huì)觸發(fā)緩沖區(qū)的頻繁合并和上下文的的頻繁切換,性能的代價(jià)會(huì)宏觀地表現(xiàn)在用戶體驗(yàn)上——掉幀。
優(yōu)化方案1:使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個(gè)圓角
UIImageView *imageView?=?[[UIImageView?alloc]initWithFrame:CGRectMake(100,?100,?100,?100)];
imageView.image?=?[UIImage?imageNamed:@"myImg"];
//開始對(duì)imageView進(jìn)行畫圖?
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size,NO,?1.0);
//使用貝塞爾曲線畫出一個(gè)圓形圖?
[[UIBezierPath?bezierPathWithRoundedRect:imageView.bounds?cornerRadius:imageView.frame.size.width]?addClip];
[imageView?drawRect:imageView.bounds];
imageView.image?=?UIGraphicsGetImageFromCurrentImageContext();
//結(jié)束畫圖?
UIGraphicsEndImageContext();
[self.view?addSubview:imageView];
優(yōu)化方案2:使用CAShapeLayer和UIBezierPath設(shè)置圓角
UIImageView *imageView=[[UIImageViewalloc]initWithFrame:CGRectMake(100,100,100,100)];
imageView.image=[UIImageimageNamed:@"myImg"];
UIBezierPath *maskPath=[UIBezierPathbezierPathWithRoundedRect:imageView.boundsbyRoundingCorners:UIRectCornerAllCornerscornerRadii:imageView.bounds.size];
CAShapeLayer *maskLayer=[[CAShapeLayeralloc]init];
//設(shè)置大小
maskLayer.frame=imageView.bounds;
//設(shè)置圖形樣子
maskLayer.path=maskPath.CGPath;
imageView.layer.mask=maskLayer;
[self.viewaddSubview:imageView];
對(duì)于方案2需要解釋的是:
??CAShapeLayer繼承于CALayer,可以使用CALayer的所有屬性值;
??CAShapeLayer需要貝塞爾曲線配合使用才有意義(也就是說才有效果)
??使用CAShapeLayer(屬于CoreAnimation)與貝塞爾曲線可以實(shí)現(xiàn)不在view的drawRect(繼承于CoreGraphics走的是CPU,消耗的性能較大)方法中畫出一些想要的圖形
??CAShapeLayer動(dòng)畫渲染直接提交到手機(jī)的GPU當(dāng)中,相較于view的drawRect方法使用CPU渲染而言,其效率極高,能大大優(yōu)化內(nèi)存使用情況。
總的來說就是用CAShapeLayer的內(nèi)存消耗少,渲染速度快,建議使用優(yōu)化方案2。
(2)shadow優(yōu)化
對(duì)于shadow,如果圖層是個(gè)簡(jiǎn)單的幾何圖形或者圓角圖形,我們可以通過設(shè)置shadowPath來優(yōu)化性能,能大幅提高性能。示例如下:
imageView.layer.shadowColor=[UIColorgrayColor].CGColor;
imageView.layer.shadowOpacity=1.0;
imageView.layer.shadowRadius=2.0;
UIBezierPath *path=[UIBezierPathbezierPathWithRect:imageView.frame];
imageView.layer.shadowPath=path.CGPath;
我們還可以通過設(shè)置shouldRasterize屬性值為YES來強(qiáng)制開啟離屏渲染。其實(shí)就是光柵化(Rasterization)。既然離屏渲染這么不好,為什么我們還要強(qiáng)制開啟呢?當(dāng)一個(gè)圖像混合了多個(gè)圖層,每次移動(dòng)時(shí),每一幀都要重新合成這些圖層,十分消耗性能。當(dāng)我們開啟光柵化后,會(huì)在首次產(chǎn)生一個(gè)位圖緩存,當(dāng)再次使用時(shí)候就會(huì)復(fù)用這個(gè)緩存。但是如果圖層發(fā)生改變的時(shí)候就會(huì)重新產(chǎn)生位圖緩存。所以這個(gè)功能一般不能用于UITableViewCell中,cell的復(fù)用反而降低了性能。最好用于圖層較多的靜態(tài)內(nèi)容的圖形。而且產(chǎn)生的位圖緩存的大小是有限制的,一般是2.5個(gè)屏幕尺寸。在100ms之內(nèi)不使用這個(gè)緩存,緩存也會(huì)被刪除。所以我們要根據(jù)使用場(chǎng)景而定。
(3)其他的一些優(yōu)化建議
??當(dāng)我們需要圓角效果時(shí),可以使用一張中間透明圖片蒙上去
??使用ShadowPath指定layer陰影效果路徑
??使用異步進(jìn)行l(wèi)ayer渲染(Facebook開源的異步繪制框架AsyncDisplayKit)
??設(shè)置layer的opaque值為YES,減少?gòu)?fù)雜圖層合成
??盡量使用不包含透明(alpha)通道的圖片資源
??盡量設(shè)置layer的大小值為整形值
??直接讓美工把圖片切成圓角進(jìn)行顯示,這是效率最高的一種方案
??很多情況下用戶上傳圖片進(jìn)行顯示,可以讓服務(wù)端處理圓角
??使用代碼手動(dòng)生成圓角Image設(shè)置到要顯示的View上,利用UIBezierPath(CoreGraphics框架)畫出來圓角圖片
(4)Core Animation工具檢測(cè)離屏渲染
對(duì)于離屏渲染的檢測(cè),蘋果為我們提供了一個(gè)測(cè)試工具Core Animation。可以在Xcode->Open Develeper Tools->Instruments中找到,如下圖:
Core Animation工具用來監(jiān)測(cè)Core Animation性能,提供可見的FPS值,并且提供幾個(gè)選項(xiàng)來測(cè)量渲染性能。如下圖:
下面我們來說明每個(gè)選項(xiàng)的功能:
Color Blended Layers:這個(gè)選項(xiàng)如果勾選,你能看到哪個(gè)layer是透明的,GPU正在做混合計(jì)算。顯示紅色的就是透明的,綠色就是不透明的。
Color Hits Green and Misses Red:如果勾選這個(gè)選項(xiàng),且當(dāng)我們代碼中有設(shè)置shouldRasterize為YES,那么紅色代表沒有復(fù)用離屏渲染的緩存,綠色則表示復(fù)用了緩存。我們當(dāng)然希望能夠復(fù)用。
Color Copied Images:按照官方的說法,當(dāng)圖片的顏色格式GPU不支持的時(shí)候,Core Animation會(huì)
拷貝一份數(shù)據(jù)讓CPU進(jìn)行轉(zhuǎn)化。例如從網(wǎng)絡(luò)上下載了TIFF格式的圖片,則需要CPU進(jìn)行轉(zhuǎn)化,這個(gè)區(qū)域會(huì)顯示成藍(lán)色。還有一種情況會(huì)觸發(fā)Core Animation的copy方法,就是字節(jié)不對(duì)齊的時(shí)候。如下圖:
Color Immediately:默認(rèn)情況下Core Animation工具以每毫秒10次的頻率更新圖層調(diào)試顏色,如果勾選這個(gè)選項(xiàng)則移除10ms的延遲。對(duì)某些情況需要這樣,但是有可能影響正常幀數(shù)的測(cè)試。
Color Misaligned Images:勾選此項(xiàng),如果圖片需要縮放則標(biāo)記為黃色,如果沒有像素對(duì)齊則標(biāo)記為紫色。像素對(duì)齊我們已經(jīng)在上面有所介紹。
Color Offscreen-Rendered Yellow:用來檢測(cè)離屏渲染的,如果顯示黃色,表示有離屏渲染。當(dāng)然還要結(jié)合Color Hits Green and Misses Red來看,是否復(fù)用了緩存。
Color OpenGL Fast Path Blue:這個(gè)選項(xiàng)對(duì)那些使用OpenGL的圖層才有用,像是GLKView或者CAEAGLLayer,如果不顯示藍(lán)色則表示使用了CPU渲染,繪制在了屏幕外,顯示藍(lán)色表示正常。
Flash Updated Regions:當(dāng)對(duì)圖層重繪的時(shí)候回顯示黃色,如果頻繁發(fā)生則會(huì)影響性能??梢杂迷黾泳彺鎭碓鰪?qiáng)性能。
以上就是本人的一些總結(jié),當(dāng)然對(duì)于UITableView的性能優(yōu)化,網(wǎng)上有很多相關(guān)的資料。如果有什么不同的觀點(diǎn),歡迎大家補(bǔ)充。