React NativeのレイアウトエンジンYogaの仕組み [前編]

こんにちは、@binaryta です。
先日「React Native OSS ペアプロ会 #3 byFACTBASE」というイベントに参加してきました。
factbase.connpass.com

OSSにcommitすることを主眼としていて、React Nativeのissueの問題に解決策を提示したり、解決できそうならPull Requestを送るといった内容です。
このイベントに参加中、レイアウトのバグを直そうとしていた際にYogaを知りYogaに興味を持ちました。
僕は「React Native OSS ペアプロ会」には初回から参加していますが、今後も定期的に開催するみたいなので、興味ある方は是非参加してみてください。

対象

Yogaはクロスプラットフォーム間で共通にレイアウト処理をするべく作られたレイアウトエンジンです。そのためiOSではObjective-C, AndroidではJava, Kotlinからも使用できるのですが、今回はReact Nativeを使って内部の仕組みを見ていくこととします。
以下対象versionです。

$ react-native --version
react-native-cli: 2.0.1
react-native: 0.56.0

前編は次のような流れで見ていきます。

  1. React Nativeのプロジェクト作成
  2. Yogaのデバッグ環境を整える

前編ではYogaのソースコードに変更を加えてデバッグできるところまで確認できたら終わりにします。
後編で内部の仕組みについて書くつもりです。

1. React Nativeのプロジェクト作成

それでは早速Yogaのレイアウトエンジンが動いている該当箇所を見ていきたいのですが、まずReact Nativeの環境を整えます。
React Nativeプロジェクトを作成していきます。

$ react-native init YogaTest
$ cd YogaTest
$ yarn install
$ react-native run-ios

とりあえず、これでReact Nativeプロジェクトが作成され、iOS Emulatorが起動します。
次にApp.jsを次のように変更します。

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 * @flow
 */

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View} from 'react-native';

type Props = {}; 
export default class App extends Component<Props> {
  render() {
    return (
      <View style={{
        flex: 1,
        flexDirection: 'column',
        justifyContent: 'space-around',
        alignItems: 'center',
      }}> 
        <Text style={{fontSize: 22}}>Yoga Test</Text>
        <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
        <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />

        <View style={{ flexDirection: 'row' }}> 
          <View style={{width: 50, height: 50, backgroundColor: 'powderblue'}} />
          <View style={{width: 50, height: 50, backgroundColor: 'skyblue'}} />
          <View style={{width: 50, height: 50, backgroundColor: 'steelblue'}} />
        </View>
      </View>
    );  
  }
}

f:id:AdwaysEngineerBlog:20180824163421p:plain:w300

変更してリロードすると上のようなアプリ画面になるはずです。
では次にYogaについてです。
Yogaはreact-nativeをインストールすると依存ライブラリとしてreact-nativeパッケージ内に付属した形で入っています。
Yogaの実際のコードはnode_modules/react-native配下に存在しています。

$ tree -C ./node_modules/react-native/ReactCommon/yoga/

./node_modules/react-native/ReactCommon/yoga/
├── Android.mk
├── BUCK
├── yoga
│   ├── Utils.cpp
│   ├── Utils.h
│   ├── YGConfig.cpp
│   ├── YGConfig.h
│   ├── YGEnums.cpp
│   ├── YGEnums.h
│   ├── YGFloatOptional.cpp
│   ├── YGFloatOptional.h
│   ├── YGLayout.cpp
│   ├── YGLayout.h
│   ├── YGMacros.h
│   ├── YGNode.cpp
│   ├── YGNode.h
│   ├── YGNodePrint.cpp
│   ├── YGNodePrint.h
│   ├── YGStyle.cpp
│   ├── YGStyle.h
│   ├── Yoga-internal.h
│   ├── Yoga.cpp
│   └── Yoga.h
└── yoga.podspec

これでYogaのファイルの場所も特定できました。

2. Yogaのデバッグ環境を整える

Yogaをデバッグするための環境を整えていきます。
React NativeアプリはJavaScriptで実装しますが、内部で処理されるレイアウト演算(Yoga)はC++で実装されています。
この言語間ブリッジの仕組みに関しては省略します。

今回はXcodeを使ってデバッグ環境を整えます。
次のコマンドを実行し、Xcodeを起動します。

$ xcode ./ios/YogaTest.xcodeproj/

次のスクリーンショットのように矢印の位置を開いて行くとYogaのレイアウト演算のメインルーチンが記述されたファイルYoga.cppが見つかるはずです。
f:id:AdwaysEngineerBlog:20180824153934p:plain

デバッグXcodeを使うと非常に簡単です。
該当の行番号をクリックするとブレークポイントを埋め込めます。
ブレークポイントを埋め込んだら⌘Rでプロジェクトをビルドし直すとデバッガが起動します。

それでは試しにレイアウト演算を行うメインルーチンの関数にブレークポイントを置いてみます。
該当行は次の箇所になります。
https://github.com/facebook/yoga/blob/1.9.0/yoga/Yoga.cpp#L2539-L2548

static void YGNodelayoutImpl(const YGNodeRef node,
                             const float availableWidth,
                             const float availableHeight,
                             const YGDirection ownerDirection,
                             const YGMeasureMode widthMeasureMode,
                             const YGMeasureMode heightMeasureMode,
                             const float ownerWidth,
                             const float ownerHeight,
                             const bool performLayout,
                             const YGConfigRef config) {

YGNodelayoutImpl関数がレイアウト演算を処理するメインルーチンとなります。
ブレークポイントを置いたら⌘Rでビルド実行します。
すると次のように関数の引数とローカル変数などの情報が確認できるようになります。
f:id:AdwaysEngineerBlog:20180824201744p:plain

とりあえずデバッグ環境が整いましたので、前編は以上です。
Yogaのレイアウトは現時点ではW3C標準規格のFlex Layout Algorithmに準拠しているのでW3CFlex Layout Algorithmに目を通しておくといいと思います。
また、Flex Layoutでよく出てくる用語についても確認しておいたほうがいいでしょう。
来週は深くまで踏み込んでいくつもりですので是非ご閲読ください。

後編はこちら。
adwaysengineerblog.hatenablog.com