【ReactNative】FlastListがスクロールできなくなるissueに取り組んだログ

@ggtmtmggです。

FlatListの小要素にmargintTop: '5%'とかを指定するとスクロールできなくなる問題に、 @saitoxuさんと取り組んだ学びのメモです。

前回までのメモはこちら https://gist.github.com/ggtmtmgg/9aaea0c6ddb1819623e3ef83e44e3b51

今回は主にObjective-Cのコードを読んでました。Objective−Cに関して初心者同然なのでそれに関する学びが多かったです。

Issueの再定義

元々は「FlatListの小要素にmargintTop: '5%'とかを指定するとスクロールできなくなる問題」だったのですが、問題の根源を追っていった結果、「ScrollViewcontentHeightを取るタイミングで小要素のheightを正しく取れていない問題」に帰着しました。

これがどのようにしては導き出されたかは割愛します。

思考のフロー

前回までの思考で、RCTScrollContentShadowViewの中で、ScrollViewのContentのLayout(要するにHeight)の計算をしているという認識を持っています。

このLayoutの計算を正しくすればIssueは解決するというのがこの瞬間の一番の仮説です。

今気になっていることを整理

  • RCTScrollContentShadowViewの実装はどうなっているのか
  • ScrollViewConetntHeightは本当に間違っているのか
    • ScrollViewContentHeightが間違っていることによってスクロールできなくなるという仮説が本当に正しいのか再調査したい
  • ScrollViewConetntHeightはいつ確定するのか
    • ContentHeightが間違っているのであれば実際に計算して値が確定する瞬間を見たい
  • ScrollViewConetntHeightはどういった計算式で確定するのか
    • ContentHeightが間違っているのであれば実際にどんな計算をしているのか知りたい

RCTScrollContentShadowViewの実装を覗いていく

https://github.com/facebook/react-native/tree/master/React/Views/ScrollView/RCTScrollContentShadowView.m

"shadow"は"follow and observe (someone) closely and secretly"という意味で使っているっぽい。にしてもShadowViewの比喩はもっと腹落ちできそう。

ざっくりRCTScrollContentShadowViewは、ScrollViewのContent部分の裏側で計算とかしてくれるViewという認識。

RCTScrollContentShadowViewRCTShadowViewを継承している。

@implementation RCTScrollContentShadowView

- (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics
            layoutContext:(RCTLayoutContext)layoutContext
{
  ...
  [super layoutWithMetrics:layoutMetrics layoutContext:layoutContext];
}

@end

[super layoutWithMetrics:layoutMetrics layoutContext:layoutContext]でScrollContentのLayoutoの計算をしてそう。

今でこそ簡単に読めるけどこのコードは[インスタンス名 メソッド名:第一引数 引数名:名前付き引数]という意味でインスタンスメソッドを実行している。

superRCTShadowViewなのそちらを見に行く

RCTShadowViewの実装を読み解く

https://github.com/facebook/react-native/tree/master/React/Views/RCTShadowView.m

  • RCTShadowView+Layout.mってファイル見かけたけどなんだろう。
  • UIEdgeInsetsってクラスについて
    • UIKitの構造体。insetというレイアウトの概念があるらしい。
  • yogaConfigはシングルトンらしい ref: ObCのシングルトンの話
  + (YGConfigRef)yogaConfig
  {
    static YGConfigRef yogaConfig;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      yogaConfig = YGConfigNew();
      YGConfigSetPointScaleFactor(yogaConfig, RCTScreenScale());
      YGConfigSetUseLegacyStretchBehaviour(yogaConfig, true);
    });
    return yogaConfig;
  }
  • Yoga.cppのL3957に必要のなさそうな分岐を発見
  // We store points for Pixel as we will use it for rounding
if (pixelsInPoint == 0.0f) {
  // Zero is used to skip rounding
  config->pointScaleFactor = 0.0f;
} else {
  config->pointScaleFactor = pixelsInPoint;
}

↓ 以下の一行だけで良さそう

config->pointScaleFactor = pixelsInPoint;
typedef NS_ENUM(unsigned int, meta_prop_t) {
  META_PROP_LEFT,
  META_PROP_TOP,
  META_PROP_RIGHT,
  META_PROP_BOTTOM,
  META_PROP_START,
  META_PROP_END,
  META_PROP_HORIZONTAL,
  META_PROP_VERTICAL,
  META_PROP_ALL,
  META_PROP_COUNT,
};
  • RCTShadowView.m L200 (__bridge void *)selfはどういう記法だろう

  • RCTComponent.hにプルリクのチャンスを発見。RCT系インスタンスがReactRootViewであるかどうかの判定がイマイチみたい。

// TODO: this is kinda dumb - let's come up with a
// better way of identifying root React views please!
static inline BOOL RCTIsReactRootView(NSNumber *reactTag)
{
  return reactTag.integerValue % 10 == 1;
}
  • objcにはid型がある
Objective-Cでは「id」型という型が定義されています。このid型は汎用的な型なのでどんな型でもセットすることができます。(JavaでいうObject型のようなものです。)
(id型は内部的には対象オブジェクトへのポインタを持っています。)

このid型も参照型の1つですが、id型に限り宣言時に「*」(アスタリスク)は不要です。

今回よくわからなくて調べたこと

  • @synthesizeってアノテーションはなんだろう
    • @propertyを使うと、コンパイル時に勝手にゲッターとセッターを定義してくれる
    • @synthesizeは、ヘッダーファイルで@propertyとして定義したプロパティをどのメンバ変数に適用するかを示すのに使う
    • 要するに@propertyではゲッターセッター名、@synthesizeでは実際のメンバ変数を指定する
    • ちなみに、synthesizeは合成するという意味
  • deallocってメソッドはなんだろう
    • NSObjectのインスタンスメソッド
    • TipsとしてObjective-Cの全てのオブジェクトはNSObjectを継承している
    • インスタンスが破棄される時に呼ばれると予想される。自信はない。
    • deallocはオブジェクトの参照の開放のために使う
    • C系のメモリの管理をもう少し理解してからまたしらべます。
  • ARCってなんだろう
    • Automatic Reference Countingの略
    • NSObjectのretain/release/autoreleaseをいい感じにしてくれる
  • 無名カテゴリってなんだろう

    • 定義済みのクラスをコンパイル時に拡張するクラス拡張というものがある
    • 無名カテゴリはそれによく似ているが、動的にクラス定義を書き換える
    • カテゴリはインスタンス変数を操作することが出来ない
    • カテゴリと無名カテゴリの違いと関係はよく分かっていない
  • 関数ではなくマクロをつかっている理由はなんだろう

  • 結果よくわからなかったです
#define RCT_SET_YGVALUE(ygvalue, setter, ...)    \
switch (ygvalue.unit) {                          \
  case YGUnitAuto:                               \
  case YGUnitUndefined:                          \
    setter(__VA_ARGS__, YGUndefined);            \
    break;                                       \
  case YGUnitPoint:                              \
    setter(__VA_ARGS__, ygvalue.value);          \
    break;                                       \
  case YGUnitPercent:                            \
    setter##Percent(__VA_ARGS__, ygvalue.value); \
    break;                                       \
}
  • + (YGConfigRef)yogaConfig;+の意味はなんだろう
  • +で定義されるのはクラスメソッド
  • -で定義されるのはインスタンスメソッド