基於0.18.1
Async batched bridge used to communicate with the JavaScript application.
分析Objective-C和JavaScript的通信機制。
Bridge承擔以下工作(或者提供接口):
A: 執行JavaScript代碼
1 - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args;
B: 管理"bridge module"
1 - (id)moduleForName:(NSString *)moduleName;
2 - (id)moduleForClass:(Class)moduleClass;
C: 創建 JavaScript 執行器
1 - (void)initModules
2 {
3 ......
4 _javaScriptExecutor = [self moduleForClass:self.executorClass];
1. 尋找起點-RCTRootView
在React-Native Based的工程中, 我們看到在AppDelegate.m文件中有以下代碼:
{
NSURL *jsCodeLocation;
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"WaterApp"
initialProperties:nil
launchOptions:launchOptions];
rootView.backgroundColor = [UIColor blackColor];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
之所以可以使用JS來進行iOS App開發,RCTRootView類是可以進行探索其原因的起點。
2. RCTBridge
接下來瀏覽類RCTRootView源碼,RCTRootView是UIView的子類,並且很簡單。其中有一個屬性:
1 @property (nonatomic, strong, readonly) RCTBridge *bridge;
通讀類RCTBridge的代碼,只有很少的200~300行。但是發現類RCTBatchedBridge繼承自類RCTBridge。
類RCTBatchedBridge是Bridge模塊的一個私有類,只被在類RCTBridge中被使用。這樣設計使得接口和繁複的實現
分離。
3. RCTCxxBridge.mm
TODO: Rewrite this section to match the modification in version 0.18.1
觀察類 RCTCxxBridge.mm中的initiailizer,發現對接口的'initJS'的調用。在這裏終於和JS '發生關係'。
- (instancetype)initWithParentBridge:(RCTBridge *)bridge
{
RCTAssertParam(bridge);
if ((self = [super initWithDelegate:bridge.delegate
bundleURL:bridge.bundleURL
moduleProvider:bridge.moduleProvider
launchOptions:bridge.launchOptions])) {
_parentBridge = bridge;
_performanceLogger = [bridge performanceLogger];
registerPerformanceLoggerHooks(_performanceLogger);
RCTLogInfo(@"Initializing %@ (parent: %@, executor: %@)", self, bridge, [self executorClass]);
/**
* Set Initial State
*/
_valid = YES;
_loading = YES;
_moduleRegistryCreated = NO;
_pendingCalls = [NSMutableArray new];
_displayLink = [RCTDisplayLink new];
_moduleDataByName = [NSMutableDictionary new];
_moduleClassesByID = [NSMutableArray new];
_moduleDataByID = [NSMutableArray new];
[RCTBridge setCurrentBridge:self];
}
return self;
}
在查看initJS方法的代碼之前,我們先來關注比較重要的方法 registerModules。
3.0 Module是什麼東西?
Module 在React Native中實際上是 可以被 JavaScript 代碼調用的模塊, 實現了接口RCTBridgeModule的類。
Module 包含有 Native類型, JavaScript源碼類型。
A): Native類型:
由Objective-C來實現相應的功能,並將接口提供給JavaScript代碼調用。
B): JavaScript源碼類型:
也就是用JavaScript寫的React Native App。這個類型的Module由 RCTSourceCode類 來代表。
3.1 registerModules 方法
TODO: Rewrite this section to match the modification in version 0.18.1
1 - (void)registerModules
2 {
3 RCTAssertMainThread();
4
5 // Register passed-in module instances
6 NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
7 for (id<RCTBridgeModule> module in self.moduleProvider ? self.moduleProvider() : nil) { // A
8 preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
9 }
10
11 // Instantiate modules
12 _moduleDataByID = [[NSMutableArray alloc] init];
13 NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy];
14 for (Class moduleClass in RCTGetModuleClasses()) { // B
15 NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
16
17 // Check if module instance has already been registered for this name
18 id<RCTBridgeModule> module = modulesByName[moduleName];
19
20 if (module) {
21 // Preregistered instances takes precedence, no questions asked
22 if (!preregisteredModules[moduleName]) {
23 // It's OK to have a name collision as long as the second instance is nil
24 RCTAssert([[moduleClass alloc] init] == nil,
25 @"Attempted to register RCTBridgeModule class %@ for the name "
26 "'%@', but name was already registered by class %@", moduleClass,
27 moduleName, [modulesByName[moduleName] class]);
28 }
29 if ([module class] != moduleClass) {
30 RCTLogInfo(@"RCTBridgeModule of class %@ with name '%@' was encountered "
31 "in the project, but name was already registered by class %@."
32 "That's fine if it's intentional - just letting you know.",
33 moduleClass, moduleName, [modulesByName[moduleName] class]);
34 }
35 } else {
36 // Module name hasn't been used before, so go ahead and instantiate
37 module = [[moduleClass alloc] init];
38 }
39 if (module) {
40 modulesByName[moduleName] = module;
41 }
42 }
43
44 // Store modules
45 _modulesByName = [[RCTModuleMap alloc] initWithDictionary:modulesByName];
46
47 /**
48 * The executor is a bridge module, wait for it to be created and set it before
49 * any other module has access to the bridge
50 */
51 _javaScriptExecutor = _modulesByName[RCTBridgeModuleNameForClass(self.executorClass)]; // C
52 RCTLatestExecutor = _javaScriptExecutor;
53
54 [_javaScriptExecutor setUp];
55
56 // Set bridge
57 for (id<RCTBridgeModule> module in _modulesByName.allValues) { // D
58 if ([module respondsToSelector:@selector(setBridge:)]) {
59 module.bridge = self;
60 }
61
62 RCTModuleData *moduleData = [[RCTModuleData alloc] initWithExecutor:_javaScriptExecutor
63 uid:@(_moduleDataByID.count)
64 instance:module];
65 [_moduleDataByID addObject:moduleData];
66
67 if ([module conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
68 [_frameUpdateObservers addObject:moduleData];
69 }
70 }
71 // E
72 [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules
73 object:self];
74 }
A): A部分用來註冊外部傳遞進來的Module。由於RCTBridge創建RCTBatchedBridge對象時,傳入的參數導致
屬性 self.moduleProvider 的值爲nil,故我們先跳過這部分,直接跳到B部分。
B): B部分的循環是將加載的ModuleClass進行註冊,註冊到成員變量 '_modulesByName'中。
(其中的 RCTGetModuleClasses()和 RCTBridgeModuleNameForClass()
參見 iOS.ReactNative-3-about-viewmanager-uimanager-and-bridgemodule 中的說明)
C): 從'_modulesByName'中獲取javaScriptExecutor,JS Executor是關鍵所在,JS Executor是執行JS代碼的。
在 initJS 方法中會用到。
D): 爲ModuleObject(模塊對象/模塊實例, 或者簡稱: 模塊)設置bridge以及ModuleData(模塊元數據),最後將實現接口RCTFrameUpdateObserver
的模塊對象添加到'_frameUpdateObservers' 中。
E): 發送通知, NativeModules已創建完畢。TODO: 該通知的觀察者做了哪些工作?
3.2 initJS 方法
TODO: Rewrite this section to match the modification in version 0.18.1
- (void)initJS
{
RCTAssertMainThread();
// Inject module data into JS context
NSMutableDictionary *config = [[NSMutableDictionary alloc] init]; // A
for (RCTModuleData *moduleData in _moduleDataByID) {
config[moduleData.name] = moduleData.config;
}
NSString *configJSON = RCTJSONStringify(@{
@"remoteModuleConfig": config,
}, NULL);
[_javaScriptExecutor injectJSONText:configJSON
asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
callback:^(NSError *error) {
if (error) {
[[RCTRedBox sharedInstance] showError:error];
}
}];
NSURL *bundleURL = _parentBridge.bundleURL;
if (_javaScriptExecutor == nil) {
/**
* HACK (tadeu): If it failed to connect to the debugger, set loading to NO
* so we can attempt to reload again.
*/
_loading = NO;
} else if (!bundleURL) {
// Allow testing without a script
dispatch_async(dispatch_get_main_queue(), ^{
_loading = NO;
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:_parentBridge
userInfo:@{ @"bridge": self }];
});
} else { 40
RCTProfileBeginEvent();
RCTPerformanceLoggerStart(RCTPLScriptDownload);
RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self]; // B
[loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) {
RCTPerformanceLoggerEnd(RCTPLScriptDownload);
RCTProfileEndEvent(@"JavaScript download", @"init,download", @[]);
_loading = NO;
if (!self.isValid) {
return;
}
static BOOL shouldDismiss = NO;
if (shouldDismiss) {
[[RCTRedBox sharedInstance] dismiss];
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shouldDismiss = YES;
});
RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
sourceCodeModule.scriptURL = bundleURL;
sourceCodeModule.scriptText = script;
if (error) {
NSArray *stack = [error userInfo][@"stack"];
if (stack) {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
withStack:stack];
} else {
[[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
withDetails:[error localizedFailureReason]];
}
NSDictionary *userInfo = @{@"bridge": self, @"error": error};
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
object:_parentBridge
userInfo:userInfo];
} else {
[self enqueueApplicationScript:script url:bundleURL onComplete:^(NSError *loadError) { // C
if (loadError) {
[[RCTRedBox sharedInstance] showError:loadError];
return;
}
/**
* Register the display link to start sending js calls after everything
* is setup
*/
NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTContextExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop];
[_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes]; // D
// E
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
object:_parentBridge
userInfo:@{ @"bridge": self }];
}];
}
}];
}
}
A): 將每個ModuleObject 元數據中的config 註冊到 JS Executor中。(config 參見 5. RCTModuleData
B): 拉取JS Bundler, 可以將JS Bundler看作JS代碼的'包'。
C): 執行Application JS code。(參見 4. JS Executor)
D): 將'_jsDisplayLink'添加到runloop中。'_jsDisplayLink'週期性的觸發的工作是什麼?
E): 發送通知 RCTJavaScriptDidLoadNotification。該通知的觀察者進行了哪些處理?
到此,焦點會集中到JS Executor上面,接下來進行JS Executor的代碼閱讀。
3.3 invalidate 方法
TODO: Rewrite this section to match the modification in version 0.18.1
3.4 React Native App源碼的執行
3.4.1 執行步驟
1: RCTBatchedBridge類的init方法中調用start方法來啓動React Native App
- (instancetype)initWithParentBridge:(RCTBridge *)bridge
{
.......
[self start]; // 1
}
return self;
}
2: 在start方法的最後,模塊初始化完畢,並且(React Native App的)源碼加載完畢後執行源碼。
模塊的初始化包含兩個部分:
A) JavaScript 模塊的初始化
B) Native 模塊的初始化。方法 initModules 完成了 Native模塊的初始化。
1 - (void)start
2 {
3 ......
4 // Synchronously initialize all native modules that cannot be loaded lazily
5 [self initModules];
6
7 ......
8 dispatch_group_notify(initModulesAndLoadSource, dispatch_get_main_queue(), ^{
9 RCTBatchedBridge *strongSelf = weakSelf;
10 if (sourceCode && strongSelf.loading) {
11 dispatch_async(bridgeQueue, ^{
12 [weakSelf executeSourceCode:sourceCode]; // 2
13 });
14 }
15 });
3: executeSourceCode方法調用方法enqueueApplicationScript來執行(React Native App的)JavaScript源碼。
- (void)executeSourceCode:(NSData *)sourceCode
{
......
RCTSourceCode *sourceCodeModule = [self moduleForClass:[RCTSourceCode class]];
sourceCodeModule.scriptURL = self.bundleURL;
sourceCodeModule.scriptData = sourceCode;
[self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) { // 3
......
// Register the display link to start sending js calls after everything is setup
NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTJSCExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop];
[_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes];
// Perform the state update and notification on the main thread, so we can't run into
// timing issues with RCTRootView
dispatch_async(dispatch_get_main_queue(), ^{
[self didFinishLoading];
[[NSNotificationCenter defaultCenter]
postNotificationName:RCTJavaScriptDidLoadNotification
object:_parentBridge userInfo:@{@"bridge": self}];
});
}];
}
4: 方法enqueueApplicationScript 最終依賴RCTJSCExecutor類型的實例 _javaScriptExecutor
來執行(React Native App的)JavaScript源碼。
1 - (void)enqueueApplicationScript:(NSData *)script
2 url:(NSURL *)url
3 onComplete:(RCTJavaScriptCompleteBlock)onComplete
4 {
5
6 [_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) { // 4
7
8 .......
9
10 [_javaScriptExecutor flushedQueue:^(id json, NSError *error)
11 {
12
13 [self handleBuffer:json batchEnded:YES];
14
15 onComplete(error);
16 }];
17 }];
18 }
4. JS Executor
TODO: Rewrite this section to match the modification in version 0.18.1
4.0 接口RCTJavaScriptExecutor
接口RCTJavaScriptExecutor定義了JS Executor需要實現的接口。在React中提供了兩個JS Executor的實現,
在React/Executors Group中:RCTWebViewExecutor、RCTContextExecutor。
WebSocket中也有一個實現: RCTWebSocketExecutor。
下面是接口RCTJavaScriptExecutor的方法聲明:
typedef void (^RCTJavaScriptCompleteBlock)(NSError *error);
typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
/**
* Abstracts away a JavaScript execution context - we may be running code in a
* web view (for debugging purposes), or may be running code in a `JSContext`.
*/
@protocol RCTJavaScriptExecutor <RCTInvalidating, RCTBridgeModule>
/**
* Used to set up the executor after the bridge has been fully initialized.
* Do any expensive setup in this method instead of `-init`.
*/
- (void)setUp;
/**
* Executes given method with arguments on JS thread and calls the given callback
* with JSValue and JSContext as a result of the JS module call.
*/
- (void)executeJSCall:(NSString *)name
method:(NSString *)method
arguments:(NSArray *)arguments
callback:(RCTJavaScriptCallback)onComplete;
/**
* Runs an application script, and notifies of the script load being complete via `onComplete`.
*/
- (void)executeApplicationScript:(NSString *)script
sourceURL:(NSURL *)sourceURL
onComplete:(RCTJavaScriptCompleteBlock)onComplete;
// 將由script表示的JavaScript腳本代表的object以objectName註冊爲全局變量。
- (void)injectJSONText:(NSString *)script
asGlobalObjectNamed:(NSString *)objectName
callback:(RCTJavaScriptCompleteBlock)onComplete;
/**
* Enqueue a block to run in the executors JS thread. Fallback to `dispatch_async`
* on the main queue if the executor doesn't own a thread.
*/
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block;
@optional
/**
* Special case for Timers + ContextExecutor - instead of the default
* if jsthread then call else dispatch call on jsthread
* ensure the call is made async on the jsthread
*/
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block;
@end
4.1 RCTJSCExecutor類
RCTJSCExecutor實現了接口RCTJavaScriptExecutor:
1 /**
2 * Uses a JavaScriptCore context as the execution engine.
3 */
4 @interface RCTJSCExecutor : NSObject <RCTJavaScriptExecutor>
5. RCTModuleData
RCTModuleData實例保存關於RCTBridgeModule實例的數據,這些數據包含: "bridge module"模塊的類(1),
"bridge module"模塊在Javascript中名字(2), "bridge module"模塊導出到JavaScript中的 Method(3),
"bridge module"模塊實例(4), the module method dispatch queue(5), "bridge module"模塊的配置信息(6)。
@property (nonatomic, strong, readonly) Class moduleClass; // 1
@property (nonatomic, copy, readonly) NSString *name; // 2
@property (nonatomic, copy, readonly) NSArray<id<RCTBridgeMethod>> *methods; // 3
@property (nonatomic, strong, readonly) id<RCTBridgeModule> instance; // 4
@property (nonatomic, strong, readonly) dispatch_queue_t methodQueue; // 5
@property (nonatomic, copy, readonly) NSArray *config; // 6
RCTModuleData的方法instance 會創建"bridge module"模塊實例:
- (id<RCTBridgeModule>)instance
{
[_instanceLock lock];
if (!_setupComplete) {
if (!_instance) {
_instance = [_moduleClass new];
}
// Bridge must be set before methodQueue is set up, as methodQueue
// initialization requires it (View Managers get their queue by calling
// self.bridge.uiManager.methodQueue)
[self setBridgeForInstance];
[self setUpMethodQueue];
[_bridge registerModuleForFrameUpdates:_instance withModuleData:self];
_setupComplete = YES;
}
[_instanceLock unlock];
return _instance;
}
6. RCTJavaScriptLoader
從本地文件系統或者遠程Server加載JavaScript。
+ (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(RCTSourceLoadBlock)onComplete
該接口實現使用了 NSURLSessionDataTask, React Native需要 iOS 7.0+ 的系統。
7. RCTSourceCode
RCTSourceCode抽象JavaScript源碼數據, 包含屬性:
scriptData 和 scriptURL
@interface RCTSourceCode : NSObject <RCTBridgeModule> // E
@property (nonatomic, copy) NSData *scriptData;
@property (nonatomic, copy) NSURL *scriptURL;
@end
本文mark下,重要的是思路,以備後面用的着就轉載過來了,版本不一樣,了,有些對不上了,可是思路還是那個