今天要講的RunLoop的應(yīng)用場景可能太簡單了,所以東西比較少橄登。因為跟UITableView沐祷、UICollectionView等的滑動優(yōu)化有關(guān)脚仔,就順便總結(jié)一下會影響UITableView歹河、UICollectionView等視圖滑動流暢的因素唠摹。
好的書籍都是值得反復(fù)看的惶看,那好的文章店雅,好的資料也值得我們反復(fù)看亿蒸。我們在不同的階段來相同的文章或資料或書籍都能有不同的收獲示辈,那它就是好文章寥茫,好書籍,好資料矾麻。?
關(guān)于iOS 中的RunLoop資料非常的少纱耻,以下資料都是非常好的。
CF框架源碼(這是一份很重要的源碼险耀,可以看到CF框架的每一次迭代弄喘,我們可以下載最新的版本來分析,或與以下文章對比學(xué)習甩牺。目前最新的是CF-1153.18.tar.gz)
RunLoop官方文檔(學(xué)習iOS的任何技術(shù)蘑志,官方文檔都是入門或深入的極好手冊;我們也可以在Xcode—>Help—>Docementation and API Reference —>搜索RunLoop—> Guides(59)—>《Threading Programming Guide:Run Loops》這篇即是)
深入理解RunLoop(不要看到右邊滾動條很長贬派,其實文章占篇幅2/5左右急但,下面有很多的評論,可見這篇文章的火熱)
RunLoop個人小結(jié)?(這是一篇總結(jié)的很通俗容易理解的文章)
sunnyxx線下分享RunLoop(這是一份關(guān)于線下分享與討論RunLoop的視頻搞乏,備用地址:https://pan.baidu.com/s/1pLm4Vf9)
iPhonedevwiki中的CFRunLoop(commonModes中其實包含了三種Mode波桩,我們通常知道兩種,還有一種是啥请敦,你知道么镐躲?)
維基百科中的Event loop(可以看看這篇文章了解一下事件循環(huán))
應(yīng)用場景
讓UITableView储玫、UICollectionView等延遲加載圖片。下面就拿UITableView來舉例說明:?
UITableView 的 cell 上顯示網(wǎng)絡(luò)圖片萤皂,一般需要兩步撒穷,第一步下載網(wǎng)絡(luò)圖片;第二步裆熙,將網(wǎng)絡(luò)圖片設(shè)置到UIImageView上桥滨。?
為了不影響滑動,第一步弛车,我們一般都是放在子線程中來做齐媒,這個不做贅述。?
第二步纷跛,一般是回到主線程去設(shè)置喻括。有了前兩篇文章關(guān)于Mode的切換,想必你已經(jīng)知道怎么做了贫奠。?
就是在為圖片視圖設(shè)置圖片時唬血,在主線程設(shè)置,并調(diào)用performSelector:withObject:afterDelay:inModes:方法唤崭。最后一個參數(shù)拷恨,僅設(shè)置一個NSDefaultRunLoopMode。
UIImage *downloadedImage = ....;
[self.myImageView performSelector:@selector(setImage:) withObject:downloadedImage afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
當然谢肾,即使是讀取沙盒或者bundle內(nèi)的圖片腕侄,我們也可以運用這一點來改善視圖的滑動。但是如果UITableView上的圖片都是默認圖芦疏,似乎也不是很好冕杠,你需要自己來權(quán)衡了。
有一個非常好的關(guān)于設(shè)置圖片視圖的圖片酸茴,在RunLoop切換Mode時優(yōu)化的例子:RunLoopWorkDistribution?
先看一下界面布局:
一個Cell里有兩個Label分预,和三個imageView,這里的圖片是非常高清的(2034 × 1525),一個界面最多有18張圖片薪捍。為了表現(xiàn)出卡頓的效果笼痹,我先自己實現(xiàn)了一下Cell,主要示例代碼:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
? ? static NSString *identifier = @"cellId";
? ? UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
? ? if (cell == nil) {
? ? ? ? cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
? ? }
? ? for (NSInteger i = 1; i <= 5; i++) {
? ? ? ? [[cell.contentView viewWithTag:i] removeFromSuperview];
? ? }
? ? UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, 300, 25)];
? ? label.backgroundColor = [UIColor clearColor];
? ? label.textColor = [UIColor redColor];
? ? label.text = [NSString stringWithFormat:@"%zd - Drawing index is top priority", indexPath.row];
? ? label.font = [UIFont boldSystemFontOfSize:13];
? ? label.tag = 1;
? ? [cell.contentView addSubview:label];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(105, 20, 85, 85)];
? ? imageView.tag = 2;
? ? NSString *path = [[NSBundle mainBundle] pathForResource:@"spaceship" ofType:@"jpg"];
? ? UIImage *image = [UIImage imageWithContentsOfFile:path];
? ? imageView.contentMode = UIViewContentModeScaleAspectFit;
? ? imageView.image = image;
? ? NSLog(@"current:%@",[NSRunLoop currentRunLoop].currentMode);
? ? [cell.contentView addSubview:imageView];
UIImageView *imageView2 = [[UIImageView alloc] initWithFrame:CGRectMake(200, 20, 85, 85)];
? ? imageView2.tag = 3;
? ? UIImage *image2 = [UIImage imageWithContentsOfFile:path];
? ? imageView2.contentMode = UIViewContentModeScaleAspectFit;
? ? imageView2.image = image2;
? ? [cell.contentView addSubview:imageView2];
UILabel *label2 = [[UILabel alloc] initWithFrame:CGRectMake(5, 99, 300, 35)];
? ? label2.lineBreakMode = NSLineBreakByWordWrapping;
? ? label2.numberOfLines = 0;
? ? label2.backgroundColor = [UIColor clearColor];
? ? label2.textColor = [UIColor colorWithRed:0 green:100.f/255.f blue:0 alpha:1];
? ? label2.text = [NSString stringWithFormat:@"%zd - Drawing large image is low priority. Should be distributed into different run loop passes.", indexPath.row];
? ? label2.font = [UIFont boldSystemFontOfSize:13];
? ? label2.tag = 4;
UIImageView *imageView3 = [[UIImageView alloc] initWithFrame:CGRectMake(5, 20, 85, 85)];
? ? imageView3.tag = 5;
? ? UIImage *image3 = [UIImage imageWithContentsOfFile:path];
? ? imageView3.contentMode = UIViewContentModeScaleAspectFit;
? ? imageView3.image = image3;
? ? [cell.contentView addSubview:label2];
? ? [cell.contentView addSubview:imageView3];
? ? return cell;
}
然后在滑動的時候,順便打印出當前的runloopMode酪穿,打印結(jié)果是:
2016-12-08 10:34:31.450 TestDemo[3202:1791817] current:UITrackingRunLoopMode
2016-12-08 10:34:31.701 TestDemo[3202:1791817] current:UITrackingRunLoopMode
2016-12-08 10:34:32.184 TestDemo[3202:1791817] current:UITrackingRunLoopMode
2016-12-08 10:34:36.317 TestDemo[3202:1791817] current:UITrackingRunLoopMode
2016-12-08 10:34:36.601 TestDemo[3202:1791817] current:UITrackingRunLoopMode
2016-12-08 10:34:37.217 TestDemo[3202:1791817] current:UITrackingRunLoopMode
可以看出凳干,為imageView設(shè)置image,是在UITrackingRunLoopMode中進行的,如果圖片很大昆稿,圖片解壓縮和渲染肯定會很耗時纺座,那么卡頓就是必然的。
查看實時幀率溉潭,我們可以在Xcode 中選擇真機調(diào)試净响,然后 Product –>Profile–>Core Animation
然后點擊開始監(jiān)測即可:?
下面就是幀率:
這里就可以使用先使用上面的方式做一次改進少欺。
[imageView performSelector:@selector(setImage:) withObject:image afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
可以保證在滑動起來順暢,可是停下來之后馋贤,渲染還未完成時赞别,繼續(xù)滑動就會變的卡頓。?
在切換到NSDefaultRunLoopMode中配乓,一個runloop循環(huán)要解壓和渲染18張大圖仿滔,耗時肯定超過50ms(1/60s)。?
我們可以繼續(xù)來優(yōu)化犹芹,一次runloop循環(huán)崎页,僅渲染一張大圖片,分18次來渲染腰埂,這樣每一次runloop耗時就比較短了飒焦,滑動起來就會非常順暢。這也是RunLoopWorkDistribution中的做法屿笼。?
簡單描述一下這種做法:?
首先創(chuàng)建一個單例牺荠,單例中定義了幾個數(shù)組,用來存要在runloop循環(huán)中執(zhí)行的任務(wù)驴一,然后為主線程的runloop添加一個CFRunLoopObserver,當主線程在NSDefaultRunLoopMode中執(zhí)行完任務(wù)休雌,即將睡眠前,執(zhí)行一個單例中保存的一次圖片渲染任務(wù)肝断。關(guān)鍵代碼看?DWURunLoopWorkDistribution類即可杈曲。
一點UITableView滑動性能優(yōu)化擴展
影響UITableView的滑動,有哪些因素呢孝情??
關(guān)于這一點鱼蝉,人眼能識別的幀率是60左右洒嗤,這也就是為什么箫荡,電腦屏幕的最佳幀率是60Hz。?
屏幕一秒鐘會刷新60次(屏幕在一秒鐘會重新渲染60次)渔隶,那么每次刷新界面之間的處理時間羔挡,就是1/60,也就是1/60秒。也就是說间唉,所有會導(dǎo)致計算绞灼、渲染耗時的操作都會影響UITableView的流暢。下面舉例說明:
1.在主線程中做耗時操作?
耗時操作呈野,包括從網(wǎng)絡(luò)下載低矮、從網(wǎng)絡(luò)加載、從本地數(shù)據(jù)庫讀取數(shù)據(jù)被冒、從本地文件中讀取大量數(shù)據(jù)军掂、往本地文件中寫入數(shù)據(jù)等轮蜕。(這一點,相信大家都知道蝗锥,要盡量避免在主線程中執(zhí)行跃洛,一般都是創(chuàng)建一個子線程來執(zhí)行,然后再回到主線程)
2.動態(tài)計算UITableViewCell的高度终议,時間過久?
在iOS7之前汇竭,每一個Cell的高度,只會計算一次穴张,后面再次滑到這個Cell這里细燎,都會讀取緩存的高度,也即高度計算的代理方法不會再執(zhí)行皂甘。但是到了iOS8,不會再緩存Cell的高度了找颓,也就是說每次滑到某個Cell,代理方法都會執(zhí)行一次叮贩,重新計算這個Cell的高度(iOS 9以后沒測試過)击狮。?
所以,如果計算Cell高度的這個過程過于復(fù)雜益老,或者某個計算使用的算法耗時很長彪蓬,可能會導(dǎo)致計算時間大于1/60,那么必然導(dǎo)致界面的卡頓捺萌,或不流暢档冬。
關(guān)于這一點,我以前的做法是在Cell中定義一個public方法桃纯,用來計算Cell高度酷誓,然后計算完高度后,將高度存儲在Cell對應(yīng)的Model中(Model里定義一個屬性來存高度)态坦,然后在渲染Cell時盐数,我們依然需要動態(tài)計算各個子視圖的高度。(可能是沒用什么太過復(fù)雜的計算或算法伞梯,時間都很短滑動也順暢)
其實玫氢,更優(yōu)的做法是:再定義一個ModelFrame對象,在子線程請求服務(wù)器接口返回后谜诫,轉(zhuǎn)換為對象的同時漾峡,也把各個子視圖的frame計算好,存在ModelFrame中喻旷,ModelFrame 和 Model 合并成一個Model存儲到數(shù)組中生逸。這樣在為Cell各個子控件賦值時,僅僅是取值、賦值槽袄,在計算Cell高度時伟阔,也僅僅是加法運算。
3.界面中背景色透明的視圖過多?
為什么界面中背景色透明的視圖過多會影響UITableView的流暢掰伸?
很多文章中都提到皱炉,可以使用模擬器—>Debug—>Color Blended Layers來檢測透明背景色,把透明背景色改為與父視圖背景色一樣的顏色狮鸭,這樣來提高渲染速度合搅。
簡單說明一下,就是屏幕上顯示的所有東西歧蕉,都是通過一個個像素點呈現(xiàn)出來的灾部。而每一個像素點都是通過三原色(紅、綠惯退、藍)組合呈現(xiàn)出不同的顏色赌髓,最終才是我們看到的手機屏幕上的內(nèi)容。在 iPhone5 的液晶顯示器上有1,136×640=727,040個像素催跪,因此有2,181,120個顏色單元锁蠕。在15寸視網(wǎng)膜屏的 MacBook Pro 上,這一數(shù)字達到15.5百萬以上懊蒸。所有的圖形堆棧一起工作以確保每次正確的顯示荣倾。當你滾動整個屏幕的時候,數(shù)以百萬計的顏色單元必須以每秒60次的速度刷新骑丸,這是一個很大的工作量舌仍。
每一個像素點的顏色計算是這樣的:?
R = S + D * (1 - Sa)?
結(jié)果的顏色 是子視圖這個像素點的顏色 + 父視圖這個像素點的顏色 * (1 - 子視圖的透明度)?
當然,如果有兩個兄弟視圖疊加通危,那么上面的中文解釋可能并不貼切铸豁,只是為了更容易理解。
如果兩個兄弟視圖重合菊碟,計算的是重合區(qū)域的像素點:?
結(jié)果的顏色 是 上面的視圖這個像素點的顏色 + 下面這個視圖該像素點的顏色 * (1 - 上面視圖的透明度)
只有當透明度為1時节芥,上面的公式變?yōu)镽 = S,就簡單的多了框沟。否則的話藏古,就非常復(fù)雜了。?
每一個像素點是由三原色組成忍燥,例如父視圖的顏色和透明度是(Pr,Pg,Pb,Pa),子視圖的顏色顏色和透明度是(Sr,Sg,Sb,Sa)隙姿,那么我們計算這個重合區(qū)域某像素點的顏色梅垄,需要先分別計算出紅、綠、藍队丝。?
Rr = Sr + Pr * (1 - Sa)靡馁,?
Rg = Sg + Pg * (1 - Sa),?
Rb = Sb + Pb * (1 - Sa)机久。?
如果父視圖的透明度臭墨,即Pa = 1,那么這個像素的顏色就是(Rr,Rg,Rb)。?
但是膘盖,如果父視圖的透明Pa 不等 1胧弛,那么我們需要將這個結(jié)果顏色當做一個整體作為子視圖的顏色,再去與父視圖組合計算顏色侠畔,如此遞推结缚。
所以設(shè)置不透明時,可以為GPU節(jié)省大量的工作软棺,減少大量的消耗红竭。
附:
轉(zhuǎn)自:https://blog.csdn.net/u011619283/article/details/5348396
更加詳細的說明,可以看繪制像素到屏幕上這篇文章喘落,這是一篇關(guān)于繪制像素的非常棒