iOS Flexbox 佈局優化

餓了麼物流技術團隊發表於2018-03-21
2017-02-12 | carlSQ | iOS

Frame AutoLayout

iOS 開發中給系統給我們提供了兩種佈局,frame 佈局 和 AutoLayout 佈局,雖然frame佈局效能很高 但面對現在越來越豐富的iPhone尺寸,為了適配各種機型需要寫更多的無營養程式碼,造成程式碼量增多。AutoLayout是iOS6 之後被引入的,由於其蹩腳、冗長的語法,直到出現一些優秀的開源第三方庫,自動佈局 才真正意義上被應用到iOS 開發中。AutoLayout將我們從座標中的點 面 思考帶到約束條件的思考,一條約束就是一個線性等式或不等式,一個檢視需要新增多個約束才能確定位置,這些約束最後組成一組線性方程組,再由Cassowary演算法算出各個檢視的frame,所以效能的關鍵問題是解線性方程組的時間複雜度。

iOS Flexbox 佈局優化

iOS Flexbox 佈局優化

Flexbox

Flexbox 是W3C在2009年提出的一種新的前端頁面佈局,目前,它已經得到了所有瀏覽器的支援。而最早將這一頁面佈局方案引入iOS開發中的是開源庫 AsyncDisplayKit。但隨著React NativeWeex在動態化領域的興起, 讓iOS開發越來越多的接觸到Flexbox 頁面佈局。

Yoga

Yoga 是由C實現的Flexbox佈局引擎,目前已經被用於在React Native 和 Weex 等開源專案中,效能和穩定性得到了很好的驗證。但不足的是Yoga只實現了W3C標準的一個子集。演算法邏輯如下圖所示:

iOS Flexbox 佈局優化

iOS Flexbox 佈局優化

基於Yoga 引擎的Flexbox 佈局優化

由於iOS 開發中frame佈局的繁瑣及Auto Layout的效能問題,我們在開發中引入了Flexbox 佈局,這樣不僅提高了佈局的便利性和效能,而且可以與專案中的React Native 和Weex 保持一致的開發體驗。為了充分提高Flexbox 佈局的效能和易用性我們維護一個開源的擴充套件FlexBoxLayout,支援鏈式呼叫,佈局方便,虛擬檢視Div,TableView 支援自動高度、佈局快取,contentView快取,和自動cache 失效機制 ScrollView 支援自適應contentSize,非同步計算佈局。 github連結請戳我

鏈式呼叫

- (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 佈局滑動效能優化

UITableViewCell 自動計算高度 和 UITableView滑動效能一直是一個重要的效能優化。FlexBoxLayout能自動計算cell高度,優化了滑動效能,使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];
  }

複製程式碼

上面的程式碼,做了以下幾件事:

  • 開發者只需要關注Cell 的ContentView 會自動重利用cell,開發者只需要實現cell的contentView具體內容.
  • 自動計算高度 會根據Flexbox佈局自動計算出高度
  • 高度快取機制 計算出的高度會自動進行快取,當滾動cell,重利用cell,後面對應的index path cell的高度的詢問都會命中快取,減少cpu的計算任務。
    iOS Flexbox 佈局優化
  • 高度快取失效機制 當資料來源發生變化時,呼叫以下幾個api
    reloadData
    insertSections:withRowAnimation:
    deleteSections:withRowAnimation:
    reloadSections:withRowAnimation:
    moveSection:toSection:
    insertRowsAtIndexPaths:withRowAnimation:
    deleteRowsAtIndexPaths:withRowAnimation:
    reloadRowsAtIndexPaths:withRowAnimation:
    moveRowAtIndexPath:toIndexPath:
複製程式碼

重新整理頁面時,會對已有的高度進行失效處理,並重新計算新的快取高度。

Demo 介面的重新整理一直接近60FPS

iOS Flexbox 佈局優化

效能對比簡單測試:

優化之前

優化之後

UIScrollView contentSize 自動計算

為了更好的適應不同的螢幕尺寸,我們在開發中會經常用到scrollview進行適配,針對flexbox佈局scrollview的contentsize大小計算問題,我們也進行了封裝處理。給scrollview增加了一個contendiv 虛擬容器,所有的子檢視只需要新增到contendiv中進行flexbox佈局。


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

  contentView.fb_contentDiv = root;

複製程式碼

設定UIScrollView的fb_contentDiv屬性,當Flexbox佈局計算完成應用到檢視上時, 在layoutSubviews函式中 UIScrollView的contentSize 會被設定大小。

Flexbox 非同步計算

現在的iOS裝置都是多核的,為了充分利用裝置的多核能力,將佈局的計算放在後臺執行緒,非同步計算,同時為了減少過多執行緒切換的開銷,在MainRunLoop即將休眠時把計算好的佈局應用到檢視上。

iOS Flexbox 佈局優化

RunLoop 在執行時,當切換時會傳送通知:

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

建立佈局計算完成事件源,當佈局完成,傳送事件源,喚醒MainRunLoop, 當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);
  }
複製程式碼

當RunLoop馬上要休眠時,在_messageGroupRunLoopObserverCallback函式處理佈局應用。

使用FlexboxLayout

如果你能覺得這個工具能夠幫到你,他是容易整合到專案的:

pod "FlexBoxLayout"
複製程式碼

GitHub地址:github.com/LPD-iOS/Fle…

如有任何智慧財產權、版權問題或理論錯誤,還請指正。

轉載請註明原作者及以上資訊。

相關文章