iOS Flexbox 布局優(yōu)化

Frame AutoLayout

iOS 開發(fā)中給系統(tǒng)給我們提供了兩種布局碌尔,frame 布局 和 AutoLayout 布局懒浮,雖然frame布局性能很高 但面對現(xiàn)在越來越豐富的iPhone尺寸梭伐,為了適配各種機(jī)型需要寫更多的無營養(yǎng)代碼读整,造成代碼量增多狂秘。AutoLayout是iOS6 之后被引入的硕噩,由于其蹩腳假残、冗長的語法,直到出現(xiàn)一些優(yōu)秀的開源第三方庫炉擅,自動布局 才真正意義上被應(yīng)用到iOS 開發(fā)中辉懒。AutoLayout將我們從坐標(biāo)中的點(diǎn) 面 思考帶到約束條件的思考,一條約束就是一個線性等式或不等式谍失,一個視圖需要添加多個約束才能確定位置眶俩,這些約束最后組成一組線性方程組,再由Cassowary算法算出各個視圖的frame袱贮,所以性能的關(guān)鍵問題是解線性方程組的時間復(fù)雜度仿便。

Flexbox

Flexbox 是W3C在2009年提出的一種新的前端頁面布局,目前攒巍,它已經(jīng)得到了所有瀏覽器的支持嗽仪。而最早將這一頁面布局方案引入iOS開發(fā)中的是開源庫 AsyncDisplayKit。但隨著React NativeWeex在動態(tài)化領(lǐng)域的興起柒莉, 讓iOS開發(fā)越來越多的接觸到Flexbox 頁面布局闻坚。

Yoga

Yoga 是由C實(shí)現(xiàn)的Flexbox布局引擎,目前已經(jīng)被用于在React Native 和 Weex 等開源項目中兢孝,性能和穩(wěn)定性得到了很好的驗(yàn)證窿凤。但不足的是Yoga只實(shí)現(xiàn)了W3C標(biāo)準(zhǔn)的一個子集。算法邏輯如下圖所示:

基于Yoga 引擎的Flexbox 布局優(yōu)化

由于iOS 開發(fā)中frame布局的繁瑣及Auto Layout的性能問題跨蟹,我們在開發(fā)中引入了Flexbox 布局雳殊,這樣不僅提高了布局的便利性和性能,而且可以與項目中的React Native 和Weex 保持一致的開發(fā)體驗(yàn)窗轩。為了充分提高Flexbox 布局的性能和易用性我們維護(hù)一個開源的擴(kuò)展FlexBoxLayout夯秃,支持鏈?zhǔn)秸{(diào)用,布局方便痢艺,虛擬視圖Div仓洼,TableView 支持自動高度、布局緩存堤舒,contentView緩存色建,和自動cache 失效機(jī)制
ScrollView 支持自適應(yīng)contentSize,異步計算布局舌缤。 github鏈接請戳我箕戳。

鏈?zhǔn)秸{(diào)用

- (void)layoutView {
  
  
  [_titleLabel fb_makeLayout:^(FBLayout *layout) {
    layout.margin.equalToEdgeInsets(UIEdgeInsetsMake(10, 0, 0, 0)).wrapContent();
  }];
  
  
  [_contentLabel fb_makeLayout:^(FBLayout *layout) {
    layout.margin.equalToEdgeInsets(UIEdgeInsetsMake(10, 0, 0, 0)).wrapContent();
  }];
  
  [_contentImageView fb_makeLayout:^(FBLayout *layout) {
    layout.margin.equalToEdgeInsets(UIEdgeInsetsMake(10, 0, 0, 0)).wrapContent();
  }];
  
  [_usernameLabel fb_makeLayout:^(FBLayout *layout) {
    layout.wrapContent().flexGrow.equalTo(@(1.0));
  }];
 
  [_timeLabel fb_makeLayout:^(FBLayout *layout) {
      layout.wrapContent().flexGrow.equalTo(@(1.0));
  }];
  
  FBLayoutDiv *div = [FBLayoutDiv layoutDivWithFlexDirection:FBFlexDirectionRow ];
  [div fb_makeLayout:^(FBLayout *layout) {
    layout.flexDirection.equalTo(@(FBFlexDirectionRow)).justifyContent.equalTo(@(FBJustifySpaceBetween)).alignItems.equalTo(@(FBAlignFlexStart)).margin.equalToEdgeInsets(UIEdgeInsetsMake(10, 0, 0, 0)).children(@[_usernameLabel,_timeLabel]);
  }];
  
  [self fb_makeLayout:^(FBLayout *layout) {
    layout.flexDirection.equalTo(@(FBFlexDirectionColumn)).margin.equalToEdgeInsets(UIEdgeInsetsMake(0, 15, 0, 15)).alignItems.equalTo(@(FBAlignFlexStart)).children(@[_titleLabel,_contentLabel,_contentImageView,div]);
  }];
  
}

UITableView Flexbox 布局滑動性能優(yōu)化

UITableViewCell 自動計算高度 和 UITableView滑動性能一直是一個重要的性能優(yōu)化某残。FlexBoxLayout能自動計算cell高度,優(yōu)化了滑動性能漂羊,使UITableView在滑動時的幀率接近60FPS.


 [self.tableView fb_setCellContnetViewBlockForIndexPath:^UIView *(NSIndexPath *indexPath) {
    return [[FBFeedView alloc]initWithModel:weakSelf.sections[indexPath.section][indexPath.row]];
  }];

  ....

  - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return [self.tableView fb_heightForIndexPath:indexPath];
  }

  - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    return [self.tableView fb_cellForIndexPath:indexPath];
  }

上面的代碼驾锰,做了以下幾件事:

  • 開發(fā)者只需要關(guān)注Cell 的ContentView
    會自動重利用cell卸留,開發(fā)者只需要實(shí)現(xiàn)cell的contentView具體內(nèi)容.
  • 自動計算高度
    會根據(jù)Flexbox布局自動計算出高度
  • 高度緩存機(jī)制
    計算出的高度會自動進(jìn)行緩存走越,當(dāng)滾動cell,重利用cell耻瑟,后面對應(yīng)的index path cell的高度的詢問都會命中緩存旨指,減少cpu的計算任務(wù)。


  • 高度緩存失效機(jī)制
    當(dāng)數(shù)據(jù)源發(fā)生變化時喳整,調(diào)用以下幾個api
    reloadData
    insertSections:withRowAnimation:
    deleteSections:withRowAnimation:
    reloadSections:withRowAnimation:
    moveSection:toSection:
    insertRowsAtIndexPaths:withRowAnimation:
    deleteRowsAtIndexPaths:withRowAnimation:
    reloadRowsAtIndexPaths:withRowAnimation:
    moveRowAtIndexPath:toIndexPath:

刷新頁面時谆构,會對已有的高度進(jìn)行失效處理,并重新計算新的緩存高度框都。

Demo 界面的刷新一直接近60FPS

性能對比簡單測試:

優(yōu)化之前
優(yōu)化之后

UIScrollView contentSize 自動計算

為了更好的適應(yīng)不同的屏幕尺寸搬素,我們在開發(fā)中會經(jīng)常用到scrollview進(jìn)行適配,針對flexbox布局scrollview的contentsize大小計算問題魏保,我們也進(jìn)行了封裝處理熬尺。給scrollview增加了一個contendiv 虛擬容器,所有的子視圖只需要添加到contendiv中進(jìn)行flexbox布局谓罗。


 FBLayoutDiv *root = [FBLayoutDiv layoutDivWithFlexDirection:FBFlexDirectionRow
                                              justifyContent:FBJustifySpaceAround
                                                  alignItems:FBAlignCenter
                                                    children:@[div1,div2]];

  contentView.fb_contentDiv = root;

設(shè)置UIScrollView的fb_contentDiv屬性粱哼,當(dāng)Flexbox布局計算完成應(yīng)用到視圖上時,
在layoutSubviews函數(shù)中
UIScrollView的contentSize 會被設(shè)置大小檩咱。

Flexbox 異步計算

現(xiàn)在的iOS設(shè)備都是多核的揭措,為了充分利用設(shè)備的多核能力,將布局的計算放在后臺線程刻蚯,異步計算绊含,同時為了減少過多線程切換的開銷,在MainRunLoop即將休眠時把計算好的布局應(yīng)用到視圖上炊汹。


RunLoop 在運(yùn)行時躬充,當(dāng)切換時會發(fā)送通知:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // 即將進(jìn)入Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // 即將處理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
    kCFRunLoopAfterWaiting  = (1UL << 6), // 剛從休眠中喚醒
    kCFRunLoopExit          = (1UL << 7), // 即將退出Loop
};

創(chuàng)建布局計算完成事件源,當(dāng)布局完成兵扬,發(fā)送事件源麻裳,喚醒MainRunLoop, 當(dāng)MainRunLoop處理完成了所有事件,馬上要休眠時,批量處理計算好的布局器钟。

  CFRunLoopObserverRef observer;
  
  CFRunLoopRef runLoop = CFRunLoopGetMain();
  
  CFOptionFlags activities = (kCFRunLoopBeforeWaiting | kCFRunLoopExit);
  
  observer = CFRunLoopObserverCreate(NULL,
                                     activities,
                                     YES,
                                     INT_MAX,
                                     &_messageGroupRunLoopObserverCallback,
                                     NULL);
  
  if (observer) {
    CFRunLoopAddObserver(runLoop, observer, kCFRunLoopCommonModes);
    CFRelease(observer);
  }
  
  CFRunLoopSourceContext  *sourceContext = calloc(1, sizeof(CFRunLoopSourceContext));
  
  sourceContext->perform = &sourceContextCallBackLog;
  
   _runLoopSource = CFRunLoopSourceCreate(NULL, 0, sourceContext);
  
  if (_runLoopSource) {
    CFRunLoopAddSource(runLoop, _runLoopSource, kCFRunLoopCommonModes);
  }

當(dāng)RunLoop馬上要休眠時津坑,在_messageGroupRunLoopObserverCallback函數(shù)處理布局應(yīng)用。

使用FlexboxLayout

如果你能覺得這個工具能夠幫到你傲霸,他是容易整合到項目的
pod "FlexBoxLayout"
github地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末疆瑰,一起剝皮案震驚了整個濱河市眉反,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌穆役,老刑警劉巖寸五,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異耿币,居然都是意外死亡梳杏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門淹接,熙熙樓的掌柜王于貴愁眉苦臉地迎上來十性,“玉大人,你說我怎么就攤上這事塑悼【⑹剩” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵厢蒜,是天一觀的道長霞势。 經(jīng)常有香客問我,道長斑鸦,這世上最難降的妖魔是什么愕贡? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮鄙才,結(jié)果婚禮上颂鸿,老公的妹妹穿的比我還像新娘。我一直安慰自己攒庵,他們只是感情好嘴纺,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著浓冒,像睡著了一般栽渴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上稳懒,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天闲擦,我揣著相機(jī)與錄音,去河邊找鬼场梆。 笑死墅冷,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的或油。 我是一名探鬼主播寞忿,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼顶岸!你這毒婦竟也來了腔彰?” 一聲冷哼從身側(cè)響起叫编,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎霹抛,沒想到半個月后搓逾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡杯拐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年霞篡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片藕施。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡寇损,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出裳食,到底是詐尸還是另有隱情,我是刑警寧澤芙沥,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布诲祸,位于F島的核電站,受9級特大地震影響而昨,放射性物質(zhì)發(fā)生泄漏救氯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一歌憨、第九天 我趴在偏房一處隱蔽的房頂上張望着憨。 院中可真熱鬧,春花似錦务嫡、人聲如沸甲抖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽准谚。三九已至,卻和暖如春去扣,著一層夾襖步出監(jiān)牢的瞬間柱衔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工愉棱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留唆铐,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓奔滑,卻偏偏與公主長得像艾岂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子档押,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

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

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫澳盐、插件祈纯、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,122評論 4 61
  • 翻譯自“Collection View Programming Guide for iOS” 0 關(guān)于iOS集合視...
    lakerszhy閱讀 3,878評論 1 22
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,306評論 25 707
  • 昨天是考試前的一晚。我們一起走了浙大叼耙,一起走回旅館腕窥,一起走錯路再找到路。我覺得這個過程還是很難得的筛婉,很美好簇爆。可是這...
    violet_340a閱讀 233評論 0 0
  • 手工/月亮公主 寶貝的手工作品爽撒,完美入蛆! 1、泥塑~門扣 材料~超輕泥 2硕勿、葉畫 3哨毁、葉拓 作者力薦~我們仨
    蓋是那么美閱讀 503評論 34 27