iOS 開發中給系統給咱們提供了兩種佈局,frame 佈局 和 AutoLayout 佈局,雖然frame佈局性能很高 但面對如今愈來愈豐富的iPhone尺寸,爲了適配各類機型須要寫更多的無養分代碼,形成代碼量增多。AutoLayout是iOS6 以後被引入的,因爲其蹩腳、冗長的語法,直到出現一些優秀的開源第三方庫,自動佈局 才真正意義上被應用到iOS 開發中。AutoLayout將咱們從座標中的點 面 思考帶到約束條件的思考,一條約束就是一個線性等式或不等式,一個視圖須要添加多個約束才能肯定位置,這些約束最後組成一組線性方程組,再由Cassowary算法算出各個視圖的frame,因此性能的關鍵問題是解線性方程組的時間複雜度。前端
Flexbox 是W3C在2009年提出的一種新的前端頁面佈局,目前,它已經獲得了全部瀏覽器的支持。而最先將這一頁面佈局方案引入iOS開發中的是開源庫 AsyncDisplayKit。但隨着React Native與Weex在動態化領域的興起, 讓iOS開發愈來愈多的接觸到Flexbox 頁面佈局。react
Yoga 是由C實現的Flexbox佈局引擎,目前已經被用於在React Native 和 Weex 等開源項目中,性能和穩定性獲得了很好的驗證。但不足的是Yoga只實現了W3C標準的一個子集。算法邏輯以下圖所示:git
因爲iOS 開發中frame佈局的繁瑣及Auto Layout的性能問題,咱們在開發中引入了Flexbox 佈局,這樣不只提升了佈局的便利性和性能,並且能夠與項目中的React Native 和Weex 保持一致的開發體驗。爲了充分提升Flexbox 佈局的性能和易用性咱們維護一個開源的擴展FlexBoxLayout,支持鏈式調用,佈局方便,虛擬視圖Div,TableView 支持自動高度、佈局緩存,contentView緩存,和自動cache 失效機制 ScrollView 支持自適應contentSize,異步計算佈局。 github連接請戳我。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]);
}];
}
複製代碼
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];
}
複製代碼
上面的代碼,作了如下幾件事:react-native
reloadData
insertSections:withRowAnimation:
deleteSections:withRowAnimation:
reloadSections:withRowAnimation:
moveSection:toSection:
insertRowsAtIndexPaths:withRowAnimation:
deleteRowsAtIndexPaths:withRowAnimation:
reloadRowsAtIndexPaths:withRowAnimation:
moveRowAtIndexPath:toIndexPath:
複製代碼
刷新頁面時,會對已有的高度進行失效處理,並從新計算新的緩存高度。api
Demo 界面的刷新一直接近60FPS 瀏覽器
性能對比簡單測試:緩存
爲了更好的適應不一樣的屏幕尺寸,咱們在開發中會常常用到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 會被設置大小。
如今的iOS設備都是多核的,爲了充分利用設備的多核能力,將佈局的計算放在後臺線程,異步計算,同時爲了減小過多線程切換的開銷,在MainRunLoop即將休眠時把計算好的佈局應用到視圖上。
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函數處理佈局應用。
若是你能以爲這個工具可以幫到你,他是容易整合到項目的:
pod "FlexBoxLayout"
複製代碼
GitHub地址:github.com/LPD-iOS/Fle…
若有任何知識產權、版權問題或理論錯誤,還請指正。
轉載請註明原做者及以上信息。