ASDK對TableView進(jìn)行性能調(diào)優(yōu)

前言

造成iOS卡頓有很多因素轰异,而造成這個問題大多是阻塞主線程導(dǎo)致用戶的交互反饋出現(xiàn)可以感知的延遲铝穷。原因主要有一下三種情形:

1.UI 渲染需要時間較長皂甘,無法按時提交結(jié)果斧散;
2.一些需要密集計算的處理放在了主線程中執(zhí)行,導(dǎo)致主線程被阻塞摊聋,無法渲染 UI 界面鸡捐;
3.網(wǎng)絡(luò)請求由于網(wǎng)絡(luò)狀態(tài)的問題響應(yīng)較慢,UI 層由于沒有模型返回?zé)o法渲染麻裁。

上面的這些問題都會影響應(yīng)用的性能箍镜,最常見的表現(xiàn)就是 UITableView 在滑動時沒有達(dá)到 60 FPS,用戶能感受到明顯的卡頓煎源。

什么是FPS

FPS是圖像領(lǐng)域中的定義色迂,是指畫面每秒傳輸幀數(shù),通俗來講就是指動畫或視頻的畫面數(shù)手销。FPS是測量用于保存歇僧、顯示動態(tài)視頻的信息數(shù)量。每秒鐘幀數(shù)愈多,所顯示的動作就會愈流暢诈悍。通常祸轮,要避免動作不流暢的最低是30。來自百度文庫

屏幕是如何渲染的侥钳?

iPhone圖像顯示工作原理


iPhone圖像顯示工作原理

iOS 的顯示系統(tǒng)是由 VSync脈沖信號驅(qū)動的适袜,VSync脈沖信號由硬件時鐘生成,大約每秒鐘發(fā)出 60 次(這個值取決設(shè)備硬件舷夺,比如 iPhone 真機(jī)上通常是 59.97)
iPhone采用的雙緩沖苦酱,安卓是三緩沖,各有優(yōu)缺點(diǎn)给猾,但都是各自的最優(yōu)解決方案

通常來說疫萤,計算機(jī)系統(tǒng)中 CPU、GPU耙册、顯示器是以上面這種方式協(xié)同工作的给僵。

1.CPU 計算好顯示內(nèi)容提交到 GPU,
2.GPU 渲染完成后將渲染結(jié)果放入第一緩沖區(qū)详拙,
3.然后硬件 Vsync到來帝际,第二緩沖會copy第一緩沖待顯示內(nèi)容,因?yàn)榻粨Q內(nèi)存地址饶辙,可認(rèn)為是瞬間完成
4.屏幕硬件經(jīng)過數(shù)模轉(zhuǎn)換傳遞而顯示出來
卡頓為什么會產(chǎn)生呢蹲诀?
所以我們做性能優(yōu)化,要從減輕cpu和gpu負(fù)擔(dān)的角度去思考

性能優(yōu)化之AutoLayout VS Frame(減輕CPU負(fù)擔(dān))

AutoLayout VS Frame

Autolayout 是自iOS 6之后 蘋果引入一種“自動布局”技術(shù)弃揽,
蘋果官方也大力推薦開發(fā)者使用此來進(jìn)行UI布局

AutoLayout其實(shí)最終會轉(zhuǎn)化為對 UIView.frame/bounds/center 等屬性的調(diào)整脯爪。

隨著視圖數(shù)量的增加, Autolayout 帶來的 CPU 消耗會呈指數(shù)級上升

所以建議大家矿微,在需要優(yōu)先考慮性能的界面痕慢,使用frame。

優(yōu)化性能之GUP

大多數(shù)的 CALayer 的屬性都是由 GPU 來繪制的涌矢,比如圖片的圓角掖举、變換、應(yīng)用紋理娜庇;但是過多的幾何結(jié)構(gòu)塔次、重繪、離屏繪制(Offscrren)以及過大的圖片都會導(dǎo)致 GPU 的性能明顯降低名秀。

Texture

Texture字面上是紋理的意思励负,以前叫做AsyncDisplayKit,是由 Facebook 開源的一個 iOS 框架匕得,能夠幫助最復(fù)雜的 UI 界面保持流暢和快速響應(yīng)继榆。下文簡稱為ASDK。

ASDK的作者是Scott Goodson,他本來是在蘋果工作裕照,開發(fā)了很多蘋果內(nèi)置的應(yīng)用攒发。后來到了Facebook,開發(fā)了ASDK這款產(chǎn)品〗希現(xiàn)在他就職于youtube惠猿。
ASDK 從開發(fā)到開源大約經(jīng)歷了一年多的時間,它其實(shí)并不是一個簡單的框架负间,更像是對 UIKit 的重新實(shí)現(xiàn)偶妖,把整個 UIKit 以及 CALayer 層封裝成一個一個 Node,將昂貴的渲染政溃、圖片解碼趾访、布局以及其它 UI 操作移出主線程,這樣主線程就可以對用戶的操作及時做出反應(yīng)董虱。
在 ASDK 中最基本的單位就是 ASDisplayNode扼鞋,每一個 node 都是對UIView 以及CALayer 的抽象。但是與UIView 不同的是愤诱,ASDisplayNode 是線程安全的云头,它可以在后臺線程中完成初始化以及配置工作。

如果按照 60 FPS 的刷新頻率來計算淫半,每一幀的渲染時間只有 16ms溃槐,在 16ms 的時間內(nèi)要完成對 UIView 的創(chuàng)建、布局科吭、繪制以及渲染昏滴,CPU 和 GPU 面臨著巨大的壓力。
從 A5 處理器之后对人,多核的設(shè)備成為了主流谣殊,原有的將所有操作放入主線程的實(shí)踐已經(jīng)不能適應(yīng)復(fù)雜的 UI 界面,所以 ASDK 將耗時的 CPU 操作以及 GPU 渲染紋理(Texture)的過程全部放入后臺進(jìn)程牺弄,使主線程能夠快速響應(yīng)用戶操作姻几。

ASTableNode

ASTableNode類似于 UIKit中的 UITableView,但是又有著很大不同猖闪。ASTableNode的cell使用ASCellNodeASCellNode通過FlexBox進(jìn)行布局肌厨,性能基本和Frame相持平培慌。
ASTableNode 并沒有像 UITableView 一樣提供一個-tableView:heightForRowAtIndexPath:協(xié)議方法來決定每個 Cell 的高度,而是由 ASCellNode 本身決定柑爸。這樣帶來的另外一個好處是吵护,動態(tài)高度的實(shí)現(xiàn)可謂是易如反掌。

實(shí)現(xiàn)代碼:

ASTableNode初始化和 UITableView是一樣的:

    ASImageNode *imageNode  = [[ASImageNode alloc] init];
    imageNode.image = [UIImage imageNamed:@"cat.jpg"];
    imageNode.frame = CGRectMake(0, 100, 100, 100);
    [imageNode addTarget:self action:@selector(imageAction) forControlEvents:ASControlNodeEventTouchUpInside];
    [self.view addSubnode:imageNode];
    
    ASNetworkImageNode *node = [[ASNetworkImageNode alloc] init];
    node.defaultImage = [UIImage imageNamed:@"cat.jpg"];
    node.URL = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1506530505693&di=68df3b2b8d7dad413af8c92a065712dc&imgtype=0&src=http%3A%2F%2Fpic.k73.com%2Fup%2Fsoft%2F2017%2F0907%2F153257_88565218.png"];
    node.frame = CGRectMake(0, 300, 100, 100);
    [node addTarget:self action:@selector(imageAction2) forControlEvents:ASControlNodeEventTouchUpInside];
    [self.view addSubnode:node];

ASTableNode的dataSource協(xié)議方法和UITableView一一對應(yīng):

/**
 返回rows數(shù)量
 */
- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section {
    // 1
    return 500;
}

/**
 相當(dāng)于UITableView的cellForIndexPath
 ASCellNode 是 UITableViewCell 的對應(yīng)封裝。本來我們可以直接返回 ASCellNode(ASDK 有一個返回這個類型的數(shù)據(jù)源方法)馅而,但是官方推薦我們使用返回塊的版本祥诽。因此這里定義了一個 ASCellNodeBlock 對象,這是一個特殊的塊瓮恭,會返回一個 ASCellNode雄坪,不需要提供任何參數(shù)。在這個塊中屯蹦,我們使用了一個自定義的 ASCellNode维哈,名為 StatusNode(后面我們會實(shí)現(xiàn)它),并調(diào)用它的初始化方法把模型傳遞進(jìn)去登澜,然后在塊的最后返回這個 ASCellNode阔挠。
 */
- (ASCellNodeBlock)tableNode:(ASTableNode *)tableNode nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ASCellNode *(^ASCellNodeBlock)() = ^ASCellNode *() {
        StatusNode *cellNode = [[StatusNode alloc] init];
        [cellNode setData];
        return cellNode;
    };
    
    return ASCellNodeBlock;
}

- (NSInteger)numberOfSectionsInTableNode:(ASTableNode *)tableNode{
    // 4
    return 1;
}

ASTableNode的delegate協(xié)議方法和UITableView有著很大的不同:

/**
 這個方法用于告訴 ASTableNode,用戶的一次下拉動作是否需要觸發(fā)異步抓取脑蠕,這里我們返回了 YES购撼,也就是不管什么情況都進(jìn)行異步抓取。我們這樣做的原因谴仙,是現(xiàn)在的后臺服務(wù)從來不告訴前端什么時候數(shù)據(jù)才會”完”,反正有數(shù)據(jù)的話服務(wù)器會返回數(shù)據(jù)迂求,沒數(shù)據(jù)的話則返回錯誤(比如“ 404 沒有數(shù)據(jù)” 之類)或者返回空結(jié)果集。所以我們根本無法事先知道數(shù)據(jù)什么時候數(shù)據(jù)已經(jīng)加載完狞甚。所以不管數(shù)據(jù)有沒有完锁摔,我們都當(dāng)做沒有完來進(jìn)行抓取,并通過服務(wù)器返回的結(jié)果來判斷哼审。這樣這個方法就沒有必要進(jìn)行任何計算了谐腰,直接返回 YES。
 */
- (BOOL)shouldBatchFetchForTableNode:(ASTableNode *)tableNode {
    return YES;
}

/**
 這個方法用于進(jìn)行一次抓取涩盾。loadPageWithContext: 方法是我們自定義的十气,它會加載一頁數(shù)據(jù),同時頁數(shù)會累加春霍,這樣每次都會加載“下一頁”砸西,除非服務(wù)器沒有數(shù)據(jù)返回。context 參數(shù)是必須的址儒,用于抓取完后通知 ASTableNode 抓取完成芹枷。
 */
- (void)tableNode:(ASTableNode *)tableNode willBeginBatchFetchWithContext:(ASBatchContext *)context
{
    [context beginBatchFetching];
    
    // 進(jìn)行數(shù)據(jù)拉取
//    [self loadPageWithContext:context];
}

- (void)tableNode:(ASTableNode *)tableNode didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    [tableNode deselectRowAtIndexPath:indexPath animated:YES];
    
    // 你自己的代碼
    // ......
    
}
ASCellNode

ASCellNode類似于 UIKit中的 UITableViewCell,使用方式和UITableViewCell類似莲趣,這里主要講ASCellNode對控件進(jìn)行FlexBox的布局方式鸳慈。

/**
 布局的時候需要對下面四種方法實(shí)現(xiàn)其一:
 *提供 layoutSpecBlock
 *覆寫 - layoutSpecThatFits: 方法
 *覆寫 - calculateSizeThatFits: 方法
 *覆寫 - calculateLayoutThatFits: 方法
 layoutSpecThatFits類似于layoutSpecBlock,其實(shí)和layoutSpecBlock沒什么不同
 */
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
    /**
     ASStackLayoutDirectionHorizontal為水平方式
     justify-content屬性定義了項(xiàng)目在主軸上的對齊方式喧伞。
     align-items屬性定義項(xiàng)目在交叉軸上如何對齊走芋。
     */
    ASStackLayoutSpec *avatarStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
                                                                               spacing:5
                                                                        justifyContent:ASStackLayoutJustifyContentStart
                                                                            alignItems:ASStackLayoutAlignItemsCenter
                                                                              children:@[_avatarNode,_nameNode,_timeNode]];
    
    ASStackLayoutSpec *likeStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
                                                                               spacing:5
                                                                        justifyContent:ASStackLayoutJustifyContentStart
                                                                            alignItems:ASStackLayoutAlignItemsCenter
                                                                              children:@[_likeNode,_viewsNode]];
    /**
     這里是垂直約束
     將avatarStack和likeStack以及標(biāo)題看做一個整體
     */
    ASStackLayoutSpec *contentStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
                                                                              spacing:5
                                                                       justifyContent:ASStackLayoutJustifyContentSpaceBetween
                                                                           alignItems:ASStackLayoutAlignItemsStretch
                                                                             children:@[avatarStack,_titleNode,likeStack]];
    /**
     外層加上整體邊框
     */
    return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(15, 15, 15, 15)
                                                  child:contentStack];
    
}

我們分別通過使用ASDK和Masonry對同樣的界面進(jìn)行性能對比绩郎,Demo 地址

通過ASDK

2017-09-28 14_15_16.gif

可以看到還是比較流暢的翁逞,及時暴力滑動肋杖,也可以達(dá)到60FPS或者接近60FPS。
下面是通過使用Masonry做的測試:

2017-09-28 14_18_31.gif

可以發(fā)現(xiàn)在快速滾動的時候有明顯卡頓挖函,F(xiàn)PS低至40以下状植,存在掉幀現(xiàn)象。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末挪圾,一起剝皮案震驚了整個濱河市浅萧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌哲思,老刑警劉巖洼畅,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異棚赔,居然都是意外死亡帝簇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門靠益,熙熙樓的掌柜王于貴愁眉苦臉地迎上來丧肴,“玉大人,你說我怎么就攤上這事胧后∮蟾。” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵壳快,是天一觀的道長纸巷。 經(jīng)常有香客問我,道長眶痰,這世上最難降的妖魔是什么瘤旨? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮竖伯,結(jié)果婚禮上存哲,老公的妹妹穿的比我還像新娘。我一直安慰自己七婴,他們只是感情好祟偷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著打厘,像睡著了一般修肠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上婚惫,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天氛赐,我揣著相機(jī)與錄音,去河邊找鬼先舷。 笑死艰管,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蒋川。 我是一名探鬼主播牲芋,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼捺球!你這毒婦竟也來了缸浦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤氮兵,失蹤者是張志新(化名)和其女友劉穎裂逐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泣栈,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卜高,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了南片。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掺涛。...
    茶點(diǎn)故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖疼进,靈堂內(nèi)的尸體忽然破棺而出薪缆,到底是詐尸還是另有隱情,我是刑警寧澤伞广,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布拣帽,位于F島的核電站,受9級特大地震影響赔癌,放射性物質(zhì)發(fā)生泄漏诞外。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一灾票、第九天 我趴在偏房一處隱蔽的房頂上張望峡谊。 院中可真熱鬧,春花似錦刊苍、人聲如沸既们。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啥纸。三九已至,卻和暖如春婴氮,著一層夾襖步出監(jiān)牢的瞬間斯棒,已是汗流浹背盾致。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留荣暮,地道東北人庭惜。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像穗酥,于是被迫代替她去往敵國和親护赊。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評論 2 355

推薦閱讀更多精彩內(nèi)容