React Native の基本原則の深い理解 (Bridge of React Native)

React Native の基本原則の深い理解 (Bridge of React Native)

この記事では、React Native の基本をすでに理解していることを前提とし、ネイティブと JavaScript が通信する際の内部動作に焦点を当てます。

メインスレッド

始める前に、React Native には 3 つのメインスレッドがあることを知っておく必要があります。

  • シャドウキュー:レイアウト作業を担当
  • メインスレッド: UIKit はこのスレッドで動作します (翻訳者注: UI マネージャー スレッドはメインスレッドとみなすことができ、主にページの操作とコントロールの描画のロジックを担当します)
  • JavaScript スレッド: JS コードを実行するスレッド

さらに、一般的に、特に指定がない限り、各ネイティブモジュールには独自のGCDキューがあります(後述)。

*シャドウキューは実際にはスレッドというよりGCDキューに似ています

ネイティブモジュール

ネイティブ モジュールの作成方法がわからない場合は、ドキュメントを読むことをお勧めします。

これは、JavaScript によって呼び出され、JavaScript を呼び出すこともできるネイティブ モジュール Person の例です。

@interface Person : NSObject <RCTBridgeModule> 
@終わり 
@implementation ロガー 
RCT_EXPORT_MODULE() 
RCT_EXPORT_METHOD(挨拶:(NSString *)名前) 
{ 
 NSLog(@"こんにちは、%@!", 名前); 
 [_bridge.eventDispatcher sendAppEventWithName:@"greeted" 本文:@{ @"name": name }];     
} 
@終わり

ここでは、RCT_EXPORT_MODULE と RCT_EXPORT_METHOD という 2 つのマクロに焦点を当て、それらの展開内容、役割、動作について説明します。

RCT_EXPORT_MODULE([js_name])

このメソッドの名前が示すように、モジュールをエクスポートしますが、この特定のコンテキストでのエクスポートとはどういう意味でしょうか? これは、ブリッジがモジュールを認識していることを意味します。

その定義は実は非常に単純です。

#RCT_EXPORT_MODULE(js_name) を定義します\ 
 RCT_EXTERN void RCTRegisterModule(クラス); \ 
 + (NSString \*)moduleName { return @#js_name; } \ 
 + (void)ロード{RCTRegisterModule(self);}

次のことを行います:

  • まず、RCTRegisterModule を外部関数として宣言します。つまり、この関数の実装はコンパイラには表示されませんが、リンク段階では使用可能になります。
  • オプションのマクロパラメータ js_name を返す moduleName メソッドを宣言して、モジュールが JS と Objective-C で異なるクラス名を持つようにします。
  • ロードメソッドを宣言します(アプリがメモリにロードされると、各クラスのロードメソッドが呼び出されます)。ロードメソッドはRCTRegisterModuleを呼び出し、ブリッジは公開されたモジュールを認識します。

RCT_EXPORT_METHOD(メソッド)

このマクロはさらに興味深いもので、メソッドに何も追加せず、指定されたメソッドを宣言するだけでなく、新しいメソッドも作成します。新しい方法は次のようになります。

+ (NSArray *)__rct_export__120 
{ 
 @[ @"", @"log:(NSString *)message" ] を返します。
}

これは、プレフィックス (__rct_export__) とオプションの js_name (この場合は空) を宣言の行番号と __COUNTER__ マクロと組み合わせて構築されます。

このメソッドの目的は、オプションの js_name とメソッド シグネチャを含む配列を返すことです。この js_name の役割は、メソッド名の競合を回避することです。

ランタイム

このセットアップ全体は、ブリッジに情報を提供して、エクスポートされたすべてのモジュールとメソッドを見つけられるようにするためのものですが、これはすべてロード時に行われます。次に、実行時にどのように使用されるかを見てみましょう。

ブリッジが初期化されたときの依存関係グラフは次のとおりです。

初期化モジュール

RCTRegisterModule が行うことは、新しいブリッジをインスタンス化するときにクラスが見つかるように、クラスを配列にプッシュすることだけです。ブリッジは、配列内のすべてのモジュールを反復処理し、各モジュールのインスタンスを作成し、ブリッジ側のインスタンスへの参照を格納し、モジュール インスタンスにブリッジへの参照を与え (両側で相互に呼び出すことができるように)、モジュール インスタンスに実行するように指定されたキューがあるかどうかを確認し、ない場合は他のモジュールとは別の新しいキューを与えます。

NSMutableDictionary *modulesByName; // = ... 
(RCTGetModuleClasses() 内のクラス moduleClass) { 
// ... 
 モジュール = [モジュールクラス new]; 
 if ([モジュールがSelectorに応答します:@selector(setBridge:)]) {
 モジュール.bridge = 自己;
 modulesByName[モジュール名] = モジュール; 
 // ... 
}

構成モジュール

これらのモジュールを取得したら、バックグラウンド スレッドで各モジュールのすべてのメソッドをリストし、__rct__export__ で始まるメソッドを呼び出して、メソッド シグネチャの文字列を取得します。これは、パラメータの実際の型がわかるので重要です。実行時には、パラメータの1つがIDであることしかわかりませんでしたが、この方法では、IDが実際にはNSStringであることがわかります。

符号なし整数メソッドカウント; 
メソッド *methods = class_copyMethodList(moduleClass, &methodCount);
(符号なし整数 i = 0; i < methodCount; i++) {
 メソッド method = methods[i];
 SELセレクター = method_getName(メソッド);
 if ([NSStringFromSelector(セレクタ) hasPrefix:@"__rct_export__"]) {
 IMP imp = method_getImplementation(メソッド);
 NSArray *エントリ = ((NSArray *(*)(id, SEL))imp)(_moduleClass, セレクタ);
 //...
 [moduleMethods addObject:/* メソッドを表すオブジェクト */];
 }
}

JavaScriptエグゼキュータの設定

JS エグゼキュータには、バックグラウンド スレッドで JS コードを初期化するなど、より複雑な作業を実行できる -setUp メソッドがあります。これにより、すべてのエグゼキュータではなく、アクティブなエグゼキュータのみが setUp メソッド呼び出しを受け取るため、作業もいくらか節約されます。

JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
_context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx];

JSON設定の挿入

JSON 構成には、次の例のように、モジュールのみが含まれます。

この構成情報はJavaScript仮想マシンのグローバル変数として保存されるため、JSサイドブリッジが初期化されるときにこの情報を使用してモジュールを作成できます。

JavaScript コードの読み込み

これは非常に簡単で、指定したプロバイダーからソース コードをロードするだけです。通常、開発中はパッケージャーから、運用中はディスクからロードします。

JavaScriptコードの実行

すべての準備が整ったら、アプリケーションのソース コードを JS 仮想マシンに読み込み、コードをコピーして解析し、実行できます。すべての CommonJS モジュールは最初の実行時に登録する必要があり、エントリ ファイルが必要です。

JSValueRef jsError = NULL; 
JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef) スクリプト); 
JSStringRef jsURL = JSStringCreateWithCFString((__bridge CFStringRef)sourceURL.absoluteString); 
JSValueRef 結果 = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, jsURL, 0, &jsError); 
JSStringRelease(jsURL); 
JSStringRelease(execJSString);

JavaScript のモジュール

JS 側では、 react-native の NativeModules を通じて、以前の JSON 構成情報で構成されるモジュールを取得できるようになりました。

動作の仕組みとしては、メソッドを呼び出すと、モジュール名、メソッド名、すべてのパラメータを含むキューに入れられ、JavsScript 実行の最後にこのキューがネイティブ モジュールに渡されて実行されます。

通話サイクル

上記のコードを使用してモジュールを呼び出すと、次のようになります。

呼び出しはネイティブから開始する必要があり、ネイティブが JS を呼び出します (この図は、JS が実行中の特定の瞬間のみをキャプチャしています)。実行プロセス中に、JS が NativeModules のメソッドを呼び出すため、この呼び出しはネイティブ側で実行する必要があるため、この呼び出しをキューに入れます。 JS の実行が完了すると、ネイティブ モジュールはキューに入れられたすべての呼び出しを反復処理し、それらの実行が完了すると、ブリッジを介してコールバックし (ネイティブ モジュールは _bridge インスタンスを介して enqueueJSCall:args: を呼び出すことができます)、再度 JS にコールバックします。

(プロジェクトをフォローしている方ならご存知でしょうが、以前はネイティブ -> JS からの呼び出しキューも存在し、vSYNC ごとにディスパッチされていましたが、起動時間を短縮するために削除されました)

パラメータタイプ

ネイティブから JS への呼び出しは簡単で、パラメータは NSArray として渡され、JSON データとしてエンコードされますが、JS からネイティブへの呼び出しではネイティブ型が必要であり、そのためには基本型 (int、float、chars など) をチェックしますが、前述のように、どのオブジェクト (構造体) でも、実行時に NSMthodSignature から十分な情報を取得できないため、型を文字列として保存します。

正規表現を使用してメソッド シグネチャから型を抽出し、RCTConvert クラスを使用して実際にオブジェクトを変換します。デフォルトでは、各型のメソッドが提供され、JSON 入力を必要な型に変換しようとします。

構造体でない限り、objc_msgSend を使用してメソッドを動的に呼び出します。arm64 には objc_msgSend_stret のバージョンがないため、NSInvocation を使用します。

すべてのパラメータを変換した後、別の NSInvocation を使用してターゲット モジュールとメソッドを呼び出します。

例:

// 特定のモジュールに次のメソッドがある場合、例: `MyModule`
RCT_EXPORT_METHOD(methodWithArray:(NSArray *) size:(CGRect)size) {}
// JS から次のように呼び出します: 
'NativeModules' が必要です。MyModule.method(['a', 1], {
 x: 0, 
 y: 0, 
 幅: 200, 
 高さ: 100 
});
// ネイティブに送信される JS キューは次のようになります。
// ** これは呼び出しのキューなので、すべてのフィールドは配列であることに注意してください ** 
@[ 
 @[ @0 ], // モジュールID 
 @[ @1 ], // メソッドID 
 @[ // 引数 
 @[ 
 @[@"a", @1], 
 @{ @"x": @0、@"y": @0、@"幅": @200、@"高さ": @100 } 
 ] 
 ]
];
// これは次の呼び出しに変換されます(疑似コード) 
NSInvocation呼び出し 
call[args][0] = GetModuleForId(@0) 
呼び出し[args][1] = GetMethodForId(@1) 
call[args][2] = obj_msgSend(RCTConvert, NSArray, @[@"a", @1]) 
call[args][3] = NSInvocation(RCTConvert, CGRect, @{ @"x": @0, ... })
電話()

スレッド

前述のように、-methodQueue メソッドを実装するか、methodQueue プロパティを有効なキューとマージすることによって実行するキューを指定しない限り、各モジュールにはデフォルトで 1 つの GCD キューがあります。例外は ViewManagers* (RCTViewManager を拡張) で、これはデフォルトで Shadow Queue を使用します。また、特別なターゲット RCTJSThread はスレッドでありキューではないため、単なるプレースホルダーです。

(実際、ビュー マネージャーは例外ではありません。基本クラスがシャドウ キューをターゲット キューとして明示的に指定しているためです)

現在のスレッドルールは次のとおりです。

  • -init および -setBridge: メインスレッドでの実行を確実にする
  • エクスポートされたすべてのメソッドはターゲットキューで実行されることが保証されます
  • RCTInvalidating プロトコルを実装すると、ターゲット キューで invalidate が呼び出されることも保証できます。
  • どのスレッドで-deallocが呼び出されるかは保証されない

JS 呼び出しのバッチが受信されると、これらの呼び出しはターゲット キューごとにグループ化され、並列に呼び出されます。

// `buckets` 内の `queue` ごとに `calls` をグループ化します 
for (バケット内のIDキュー) { 
 ディスパッチブロック_tブロック = ^{ 
 NSOrderedSet *calls = [buckets objectForKey:queue]; 
 for (NSNumber *indexObj 呼び出し内) { 
 // 実際に呼び出す 
 } 
 }; 
 if (キュー == RCTJSThread) { 
 [_javaScriptExecutor は JavaScriptQueue 上でブロックを実行します]; 
 } そうでない場合 (キュー) { 
 キュー、ブロックをdispatch_asyncします。 
 } 
}

結論

React Native ブリッジングの仕組みについての詳細な概要は以上です。これが、より複雑なモジュールを構築したい人や、コア フレームワークに貢献したい人の役に立つことを願っています。

React Native のコア原則 (React Native Bridge) の詳細な理解に関するこの記事はこれで終わりです。React Native の原則に関するその他のコンテンツについては、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

以下もご興味があるかもしれません:
  • React.Childrenの詳しい使い方
  • React Hooksの使用例
  • React+Koa によるファイルアップロードの実装例
  • Reactフックとzarmコンポーネントライブラリ構成に基づいてh5フォームページを開発するためのサンプルコード
  • React antd タブの切り替えによりサブコンポーネントが繰り返し更新される
  • ReactJs 基礎チュートリアル - 基本編
  • ReactRouterの実装
  • React.cloneElement の使い方の詳しい説明

<<:  Centos 6.9 に MySQL をインストールするための詳細なチュートリアル

>>:  Linux における「!」の知られざる使用法のまとめ

推薦する

MySQL クエリ キャッシュとバッファ プール

1. キャッシュ - クエリキャッシュ次の図は、MySQL 公式サイトから提供されています: MyS...

CentOS 7にChromeブラウザをインストールする方法

この記事では、CentOS 7 に Chrome ブラウザをインストールする方法を紹介します。詳細は...

Linux の PHP に XML 拡張機能をインストールする詳細な手順

PHP Linux に XML 拡張機能をインストールする1. PHPインストールソースパッケージを...

CSS3 を使用した背景ぼかし効果の 3 つの例

導入から始めず、いきなり本題に入りましょう。通常の背景ぼかし効果は次のとおりです。 プロパティを使用...

Docker イメージの最適化 (1.16GB から 22.4MB)

目次最適化の第一歩: 軽量ベースイメージの使用第2段階の最適化:多段階構築Docker は、ソフトウ...

jsはFileReaderを使用してローカルファイルまたはBLOBを読み取ります

目次FileReaderはローカルファイルまたはBLOBを読み取ります1. FileReaderの使...

HTML における li タグの水平配置の例

ほとんどのナビゲーション バーは、下の図に示すように水平に配置されていますが、これはどのように実現さ...

CSS で div 凹角スタイルを実装するサンプル コード

通常の開発では、凸型の丸い角、つまり border-radius 属性を使用するのが一般的です。凹角...

Linux コマンドラインで他のユーザーと通信する方法

Linux のコマンドラインで他のユーザーにメッセージを送信するのは簡単です。これを行うコマンドは多...

MySQL ページング分析の原理と効率改善

MySQL ページング分析の原理と効率改善PERCONA PERFORMANCE CONFERENC...

Vueエンジニアがカプセル化しなければならない埋め込み命令の知識のまとめ

目次序文指導の基本フック機能フック関数のパラメータ文章使い方とアイデア成し遂げる汎用性を高める要約す...

CSS画像結合技術(スプライト画像)の詳しい説明

CSS画像結合技術1. 画像のステッチ画像ステッチング技術は、個々の画像を収集する技術です。画像の多...

Ubuntu 16.4 で完全に分散された Hadoop 環境を構築するための実践的なチュートリアル

序文この記事は主にubantu 16.4 Hadoop完全分散構築に関する関連コンテンツを紹介し、皆...

Linux でファイルの作成時間を取得する方法と実践的なチュートリアル

背景ファイルの作成時刻を取得する必要がある場合があります。例えば: 「xtrabackup スキーマ...

docker-machineの使い方の詳しい説明

Docker-machineはDockerが公式に提供しているDocker管理ツールです。これは d...