UICollectionView滑動(dòng)流暢性優(yōu)化
貴站的圖片支持我真的服了昵骤,要獲得更好的圖片體驗(yàn)請(qǐng)自行百度另一友站的同一標(biāo)題的文章
前言
初始的collection view在滑動(dòng)時(shí)都是十分流暢的畴嘶,然而因?yàn)閏ollection view cell 加載更多的內(nèi)容時(shí)因?yàn)橹骶€程耗用太多性能而導(dǎo)致主線程出現(xiàn)堵塞仅政,導(dǎo)致原本流暢的滑動(dòng)出現(xiàn)卡頓的情況嚼摩。
<center>
<img style="border-radius: 0.3125em;
box-shadow: 0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.08);"
src="https://raw.githubusercontent.com/MrYu4/MyUploadPicture/master/20190223175348-beginKaDun.gif">
<div style="color:orange; border-bottom: 1px solid #ffffff;
display: inline-block;
color: #000000;
padding: 2px;"></div>
</center>
collection view加載cell時(shí)一般會(huì)常用復(fù)用池磕蛇,所以滑動(dòng)時(shí)每要顯示一個(gè)cell都會(huì)調(diào)用協(xié)議中- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath;
的方法频敛,而在這方法中除了返回復(fù)用池中的cell霸褒,程序猿還可以趁機(jī)在其中調(diào)用cell自定義的讀取數(shù)據(jù)的方法(姑且稱之為-(void)loadData:
)司顿,在本文中即將展示如何將- (void)loadData:
中的耗時(shí)操作放入NSOperationQueue中并發(fā)進(jìn)行從而不影響collection view的滑動(dòng)芒粹,同時(shí)在用戶快速滑動(dòng)中,單個(gè)cell的NSOperationQueue
中會(huì)堆積大量operation
的情況大溜,也將在本文中提出應(yīng)對(duì)方法化漆。
NSOperationQueue和NSInvocationOperation
NSOperationQueue將線程操作以隊(duì)列的形式展現(xiàn),初始化代碼如下:
_queue = ({
NSOperationQueue *q = [[NSOperationQueue alloc]init];
//設(shè)置最大并行操作數(shù)為1相當(dāng)于將queue設(shè)置為串行線程
q.maxConcurrentOperationCount = 1;
q;
});
每一個(gè)operation都是這個(gè)隊(duì)列中的一個(gè)元素钦奋,而這個(gè)元素在滿足條件的情況下可以在不用執(zhí)行就從隊(duì)列中移除座云,我們可以利用這種特性來取消其中一些未執(zhí)行但是不必執(zhí)行的operation ,關(guān)鍵代碼如下:
if (self.operationQueue.operationCount >= 2) {
[self.operationQueue cancelAllOperations];
}
NSInvocationOperation *op = [[NSInvocationOperation alloc]
initWithTarget:self
selector:@selector(operationSelector:)
object:dic];
[self.operationQueue addOperation:op];
其中-(void)cancelAllOperations
可以讓隊(duì)列里的所有待執(zhí)行的operation移除出隊(duì)列(詳情看官方文檔),要注意付材,如果執(zhí)行此方法時(shí)有operation正在執(zhí)行朦拖,那么那些正在執(zhí)行的operation不會(huì)被強(qiáng)行中止并取消。
Canceling the operations does not automatically remove them from the queue or stop those that are currently executing. For operations that are queued and waiting execution, the queue must still attempt to execute the operation before recognizing that it is canceled and moving it to the finished state.——蘋果官方開發(fā)文檔
NSInvocationOperation是NSOperation的子類厌衔,它和NSBlockOperation一樣都可以作為元素添加到NSOperationQueue
中等待執(zhí)行璧帝,最主要的區(qū)別在于前者帶的是@selector
后者帶的是block
。
異步操作中因視圖原因?qū)е鲁绦虮罎r(shí)的應(yīng)對(duì)方法
原本高高興興把耗時(shí)的代碼都放到隊(duì)列中運(yùn)行時(shí)卻發(fā)現(xiàn)在涉及到視圖操作的地方容易出現(xiàn)錯(cuò)亂葵诈,甚至是BAD_XXXX之類的崩潰時(shí)裸弦,就需要檢查@selector
中是否包含類似addSubview
,removeFromSubview
之類的傻X操作(每讀取一次數(shù)據(jù)就把view
移出來/放進(jìn)去是什么鬼操作祟同?!View:我來了理疙,我又走了晕城,我來了,我又走了窖贤,你打我呀……)砖顷,遇到這種代碼第一想到的是盡量將其移出Operation
,最好是將其放到初始化的方法赃梧,但是還有些情況是有些視圖的屬性受到數(shù)據(jù)的影響滤蝠,需要等耗時(shí)操作結(jié)束之后才能確定視圖的屬性,這個(gè)時(shí)候就要用到GCD的方法了授嘀,如下:
- (void)optimizeOperation:(NSString *)dataStr {
//模擬十分耗時(shí)間的操作,注意物咳,這里不能放在main_queue里面執(zhí)行,否則時(shí)間還是消耗在了主線程里面
[NSThread sleepForTimeInterval:0.05];
if ([dataStr isEqualToString:self.dataStr]) {
dispatch_async(dispatch_get_main_queue(), ^{
self.titleLb.text = dataStr;
});
}
}
要注意dispatch_async(dispatch_get_main_queue(), ^{})
中不要有耗時(shí)的方法蹄皱,我們到目前為止的鋪墊都是為了讓占用主線程的滑動(dòng)操作不要被其他操作堵塞览闰,如果把耗時(shí)操作放進(jìn)去的話就前功盡棄了。
<center>
<img style="border-radius: 0.3125em;
box-shadow: 0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.08);"
src="https://raw.githubusercontent.com/MrYu4/MyUploadPicture/master/20190223150504-FinalLook.gif">
<div style="color:orange; border-bottom: 1px solid #ffffff;
display: inline-block;
color: #000000;
padding: 2px;">很難想象其中每個(gè)cell的讀取方法中間會(huì)有0.05s的耗時(shí)方法</div>
</center>
附帶demo地址巷折,本文所涉及的思路體現(xiàn)在TextCell.m中:https://github.com/MrYu4/OptimizeCollectionView
可能存在的疑問
- 可是我就是不想用NSInvocationOperation啊压鉴,我用NSBlockOperation不行嗎?
行
-
if (self.operationQueue.operationCount >= 2)
這個(gè)判斷語句怎么理解锻拘?
隊(duì)列中如果有兩個(gè)或兩個(gè)以上個(gè)待出隊(duì)列的
operation
油吭,那么第一個(gè)可能是正在執(zhí)行中的,第二個(gè)或者之后都是未執(zhí)行的(忘了說了署拟,queue設(shè)置了最多可并發(fā)運(yùn)行一個(gè)operation
)婉宰,這個(gè)時(shí)候正好是有新的operation
等待加入,就說明以往的operation
沒什么用了芯丧,這個(gè)時(shí)候就可以進(jìn)行剪枝操作芍阎,把operation
除去節(jié)省性能世曾。
- 接著上個(gè)問題缨恒,為什么不是>=1呢
[self.operationQueue cancelAllOperations];
能立刻去除的是未執(zhí)行的operation
,但正在執(zhí)行的不會(huì)除掉轮听,也就是在cancelAllOperations
之前隊(duì)列內(nèi)元素個(gè)數(shù)=1時(shí)骗露,就基本上可以確定隊(duì)列中沒有可以除掉的operation
了。
求打賞
<center>
<img style="border-radius: 0.3125em;
box-shadow: 0 2px 4px 0 rgba(34,36,38,.12),0 2px 10px 0 rgba(34,36,38,.08);"
src="https://raw.githubusercontent.com/MrYu4/MyUploadPicture/master/20190223153126-merge_from_ofoct%20(1).png">
<div style="color:orange; border-bottom: 1px solid #ffffff;
display: inline-block;
color: #000000;
padding: 2px;"></div>
</center>