rn源码ios_React native 源码之IOS初始化

本文深入探讨了React Native在iOS中的初始化过程,包括RCTRootView、RCTBridge和RCTBatchedBridge的角色。通过bundleURL和launchOptions,RN创建RCTBridge实例,接着初始化RCTBatchedBridge,负责加载JS Bundle和初始化Native Modules。JSBundle最终转化为NSData,而Native Modules在编译阶段即被登记,运行时实例化并提供给JS端使用。整个流程中,CADisplayLink用于同步屏幕刷新,确保流畅的用户体验。
摘要由CSDN通过智能技术生成

首先,我们要知道React native是一个跨三端的技术。它使用JavaScript代码来生成原生App的应用。在这里,Javascript所起的作用,像是一位产品经理,JS的代码起到的是一个指示的作用。而Native的Java与OC,则像是一位程序员,在JS的指示下完成实际的UI生成等工作。从某个角度来说,原生相当于JS的一个解释器。

IOS通过RCTRootView初始化React native。RCTRootView暴露出来的默认初始化函数如下:

- (instancetype)initWithBundleURL:(NSURL*)bundleURL moduleName:(NSString*)moduleName initialProperties:(NSDictionary*) initialProperties launchOptions:(NSDictionary *)launchOptions

进入到该方法,我们可以看到,RN通过bundleURL与launchOptions参数初始化了一个RCTBridge *bridge变量,然后通过bridge与moduleName,initialProperties继续初始化RCTRootView。

RCTBridge与RCTBatchedBridge的初始化:

顾名思义,RCTBridge是Native与JS交互过程中Native方的桥接类。但RCTBatchedBridge是什么呢?

RCTBatchedBridge继承了RCTBridge,JS的操作,加载等工作皆在该类中进行。对RCTBridge实例,都会维护一个RCTBatchedBridge实例,已完成面向JS端的操作。一般而言,一个RN应用只需要一个RCTBridge实例,该实例维护的RCTBatchedBridge实例,会保存为RCTBridge类中的static变量。

在一般情况下,RCTBridge的初始化只需要bundleURL参数:

- (instancetype)initWithDelegate:nil bundleURL:bundleURL moduleProvider:nil launchOptions:nil

该方法实际上是setUP函数的包装,其主要功能就是创建并启动本类的RCTBatchedBridge实例。

RCTBatchedBridge不会在RCTBridge实例之外初始化,其init函数依赖于包含它的RCTBridge实例(ParentBridge):

- (instancetype)initWithParentBridge:(RCTBridge *)bridge

RCTBatchedBridge中发生的事情

RCTBatchedBridge完成初始化后,ParentBridge会调用RCTBatchedBridge实例的start方法,Start 方法中主要的工作如下:

在start函数中,首先同时进行JSbundle代码的加载与NativeModule的初始化(通过dispatch_group_tinitModulesAndLoadSource)

1、JSbundle代码的加载:

JS Bundle加载后存储在NSData型数据中:

[self loadSource:^(NSData *source){

sourceCode = source;

} onProgress:nil]

=》

[RCTJavaScriptLoader loadBundleAtURL:self.bundleURL onProgress:onProgress onComplete:^(NSError *error, NSData *source, int64_t sourceLength) {onSourceLoad(error, source, sourceLength);}];

=》

NSData *source = [NSData dataWithContentsOfFile:scriptURL.path options:NSDataReadingMappedIfSafe error:&error];

可以看到,JSBundle最终转化成为NSData数据。

2、初始化Native module

Native module,指的是在RN运行中使用的Native类。举例来说,ReactNative的JS执行器RCTJSCExecutor,就是一个NativeModule。此外,用户为RN提供原生函数的类,如CodePush等,也是Native module。

1)静态阶段

Native module都存储在staticNSMutableArray *RCTModuleClasses;继承了协议并在类中使用了协议提供的宏RCT_EXPORT_MODULE。

RCTBridgeModule内部如下所示:

{

+ (NSString *)moduleName;

@property (nonatomic, weak, readonly) RCTBridge *bridge;

…...

}

宏RCT_EXPORT_MODULE的展开如下:

( RCT_EXPORT_MODULE(jsName))

=>

{

extern __attribute__((visibility("default")))void RCTRegisterModule(Class);

+ (NSString *) moduleName {

return @“jsName";

}

+ (void)load {

RCTRegisterModule(self);

}

}

可以看到,对一个Native module,moduleName会被RCT_EXPORT_MODULE覆盖。

而void RCTRegisterModule(Class)关联到了RCTBridge类中的同名函数。(不太理解,应该是通过extern __attribute__((visibility("default")))声明关联过去的。

RCTBridge中的RCTRegisterModule的实现如下:

void RCTRegisterModule(Class moduleClass)

{

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

RCTModuleClasses = [NSMutableArray new];

});

[RCTModuleClasses addObject:moduleClass];

}

也就是说,在编译阶段,*RCTModuleClasses中就存储了各个Native module的Class (OC中类函数的self是class变量)。

2)实例化module并存储

start方法中调用initModulesWithDispatchGroup方法用来初始化native module,而这些native module会转换成为RCTModuleData提供给JS端使用。

在默认流程中,首先初始化的是Javascript执行器_javaScriptExecutor,如没有自定义,则使用RCTJSCExecutor执行结果。

然后,RN遍历RCTGetModuleClasses,将RCTGetModuleClasses中的各个Class提取出来,并存储类名。对每个moduleClass,通过moduleClass生成需要的RCTModuleData *moduleData,生成的moduleData就是JS可以使用的module。这些moduleData的存储是通过以下几个数据结构实现的:

NSDictionary *_moduleDataByName;

NSArray *_moduleDataByID;

NSArray *_moduleClassesByID;

2.1)RCTModuleData 初始化

RCTModuleData是Native module的包装类,Native module就是通过该类的实例暴露给JS端的。RCTModuleData实例变量中的_moduleClass与_instance分别存储Native module 的类变量与实例变量。_bridge则是当前的RCTBatchedBridge

initWithModuleClass:(Class)moduleClass

initWithModuleInstance:(id)instance

在RCTModuleData初始化时,会根据类中是否含有函数来设定变量,决定如何加载Native module。

_implementsBatchDidComplete = [_moduleClass instancesRespondToSelector:@selector

(batchDidComplete)];

_implementsPartialBatchDidFlush = [_moduleClass instancesRespondToSelector:@selector

(partialBatchDidFlush)];

_requiresMainQueueSetup = !_instance && [_moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod;

_hasConstantsToExport = [_moduleClass instancesRespondToSelector:@selector(constantsToExport)];

_requiresMainQueueSetup表示有Native module有自定义的初始化方法,_hasConstantsToExport表示Native module有输出的常量(通过constantsToExport)。这两者为真时,Native module不会使用懒加载机制,而是生成ModuleData后立即初始化_instance。

一般而言,通过moduleClass初始化的RCTModuleData实例变量有lazyInit机制 。_instance的实例化在调用[moduleDatainstance]的时刻进行的。

[moduleDatainstance]主要是对[moduleDatasetUpInstanceAndBridge]的封装。[moduleDatasetUpInstanceAndBridge]可分为4个主要步骤:

实例化:_instance= [_moduleClassnew];

将_bridge关联到实例上:[(id)_instancesetValue:_bridgeforKey:@"bridge"];

为实例建立方法执行队列:

_queueName= [NSStringstringWithFormat:@"com.facebook.react.%@Queue",self.name];

_methodQueue=dispatch_queue_create(_queueName.UTF8String,DISPATCH_QUEUE_SERIAL);

[(id)_instancesetValue:_methodQueueforKey:@"methodQueue"];

4.初始化结束后通知_bridge

4.1 将Module注册为观察者:[_bridgeregisterModuleForFrameUpdates:_instancewithModuleData:self];(在RCTDispalyLink上细说)

RCTDisplayLink是一个CADisplayLink的包装。其主要数据结构如下:

{

CADisplayLink*_jsDisplayLink;

NSMutableSet *_frameUpdateObservers;

NSRunLoop*_runLoop;

}

CADisplayLink

CADisplayLink是一个能让我们以和屏幕刷新率相同的频率将内容画到屏幕上的定时器。我们在应用中创建一个新的 CADisplayLink 对象,把它添加到一个runloop中,并给它提供一个 target 和selector 在屏幕刷新的时候调用。

一但CADisplayLink以特定的模式注册到runloop之后,每当屏幕需要刷新的时候,runloop就会调用CADisplayLink绑定的target上的selector,这时target可以读到 CADisplayLink 的每次调用的时间戳,用来准备下一帧显示需要的数据。例如一个视频应用使用时间戳来计算下一帧要显示的视频数据。在UI做动画的过程中,需要通过时间戳来计算UI对象在动画的下一帧要更新的大小等等。

RCTDisplayLink通过如下方法初始化CADisplayLink:

_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_jsThreadUpdate:)];

这样系统在每次屏幕刷新时会调用_jsThreadUpdate。

对所有的RCTModuleData的实例,只有实现了协议RCTFrameUpdateObserver的module,才会被注册到RCTDisplayLink的_frameUpdateObservers中。而RCTFrameUpdateObserver协议如下:

/**

* Method called on every screen refresh (if paused != YES)

*/

- (void)didUpdateFrame:(RCTFrameUpdate*)update;

/**

* Synthesize and set to true to pause the calls to -[didUpdateFrame:]

*/

@property (nonatomic, readonly, getter=isPaused) BOOL paused;

/**

* Callback for pause/resume observer.

* Observer should call it when paused property is changed.

*/

@property (nonatomic, copy) dispatch_block_t pauseCallback;

而_jsThreadUpdate最主要的功能就是调用_frameUpdateObservers中每一个module的- (void)didUpdateFrame:(RCTFrameUpdate*)update;

这样

4.2 向_bridge发送完成通知

2.2)RCTModuleData config:

RCTModuleData的config变量是一个Array,里面存放了对应的Native module需要暴露给JS端的数据。其生成过程如下:

2.2.1)gatherConstants

Native module通过实现constantsToExport函数来输出常量

2.2.2)gatherMethods

Methods指的是Native module中暴露给JS的方法。这些方法在Native module类中通过宏RCT_EXPORT_METHOD来包装,在RCTModuleData中则储存为RCTModuleMethod型变量。

宏RCT_EXPORT_METHOD

RCT_EXPORT_METHOD(method)=> RCT_REMAP_METHOD(, method)

RCT_REMAP_METHOD(js_name, method) => RCT_EXTERN_REMAP_METHOD(js_name, method)\- (void)method

RCT_EXTERN_REMAP_METHOD(js_name, method)  =>

+ (NSArray *)RCT_CONCAT(__rct_export__, RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) {

return @[@#js_name, @#method];

}

__COUNTER__宏,这个宏展开为一个整数,初始化为0,每使用一次就+1

这样

RCT_EXPORT_METHOD(jumpWeb:(NSString *)url resolver:(RCTPromiseResolveBlock)resolve rejecter (RCTPromiseRejectBlock)reject) {

[[NSNotificationCenter defaultCenter] postNotificationName:@"openWebView" object:nil userInfo:@{@"url":url}];

}

=>

+ (NSArray *)__rct_export__(lineNum)(randomNumber) {

return @[@"", @"jumpWeb:(NSString *)url resolver:(RCTPromiseResolveBlock)resolve rejecter (RCTPromiseRejectBlock)reject"]

}

-(void)jumpWeb:(NSString *)url resolver:(RCTPromiseResolveBlock)resolve rejecter (RCTPromiseRejectBlock)reject {

[[NSNotificationCenter defaultCenter] postNotificationName:@"openWebView" object:nil userInfo:@{@"url":url}];

}

RCT_REMAP_METHOD(getVersion,getUserLoginResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject){

resolve(appVersion);

}

=>

+ (NSArray *)__rct_export__(lineNum)(randomNumber) {

return @[@"getVersion", @"getUserLoginResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject"]

}

-(void)getUserLoginResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {

resolve(appVersion);

}

也就说, RCT_EXPORT_METHOD 宏,会在实例方法前生成一个类方法。

RCTModuleData中,读取_moduleClass的methodsList:

Method *methods = class_copyMethodList(object_getClass(cls), &methodCount);

如果methods中的元素有前缀__rct_export__,就会执行该函数,将结果保存为entries:

IMP imp = method_getImplementation(method);

NSArray *entries =((NSArray *(*)(id, SEL))imp)(_moduleClass, selector);

然后初始化RCTModuleMethod

id moduleMethod =[[RCTModuleMethod alloc] initWithMethodSignature:entries[1] JSMethodName:entries[0] moduleClass:_moduleClass];

然后将这些Methods存储到_methods中。

2.2.3)classifyMethods

对方法数组进行分类,如果函数使用了 “RCTPromise”,promiseMethods中会存放该函数在_methods中的位置。

还有一个syncMethods,这个一直为空,没找到调用。

最后,生成的config如下:

NSArray *config = @[self.name,constants,methods,promiseMethods,syncMethods)

RCT_EXPORT_MODULE(ToolModule)

RCT_EXPORT_METHOD(call:(NSString*)phone resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject);

RCT_REMAP_METHOD(getVersion,getUserLoginResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject);

RCT_REMAP_METHOD(cleanWebViewCache,cleanWebViewCacheResolver:(RCTPromiseResolveBlock)resolve

rejecter:(RCTPromiseRejectBlock)reject);

=>

[ ToolModule,,[call,getVersion,cleanWebViewCache],[0,1,2],]

3)启动JSExecutor并将ModuleConfig写入到JScontext

3.1JSExecutor与初始化

RCTBatchedBridge保持一个idjavaScriptExecutor。这是JAVASCRIPT的执行器。这一步就是RCTBatchedBridge的初始化。

JSExecutor是native端的JS代码解释器。RN默认使用RCTJSCExecutor,而而该类主要是对JavaScriptCore进行包装。

RCTJSCExecutor有以下几个关键数据:

NSThread_javaScriptThread :在init时创建并开启,JSCore的执行都在该线程上。

RCTJavaScriptContext*_context; : 就是JSContext,主要是关联_javaScriptThread与JScontext

JSValueRef_batchedBridgeRef; : JS端bridge的JSValue

JSExecutor初始化的主要工作是配置JS的执行环境,其简写代码如下:

//初始化JSContext

JSGlobalContextRefcontextRef =JSGlobalContextCreateInGroup();

self.context =[JSContext(contextRef)contextWithJSGlobalContextRef:contextRef]

//关联JSContext与_javaScriptThread

NSMutableDictionary*threadDictionary = [[NSThreadcurrentThread]threadDictionary];

threadDictionary[RCTFBJSContextClassKey] =JSC_JSContext(contextRef);

threadDictionary[RCTFBJSValueClassKey] =JSC_JSValue(contextRef);

//threadDictionary:每个线程都维护了一个键-值的字典,它可以在线程里面的任何地方被访问。你可以使用该字典来保存一些信息,这些信息在整个线程的执行过程中都保持不变。

//配置Context,JS端可以通过Global访问OC的代码

context[@"nativeRequireModuleConfig”] =>^NSArray*(NSString*moduleName) {[strongSelf->_bridgeconfigForModuleName:moduleName];}

context[@"nativeFlushQueueImmediate"] = ^(NSArray *calls){[strongSelf->_bridgehandleBuffer:callsbatchEnded:NO];}

context[@"nativeCallSyncHook"]= ^(NSArray *calls){idresult = [strongSelf->_bridgecallNativeModule:modulemethod:methodparams:args];

}

//也就是说,在JS代码中,global.nativeRequireModuleConfig,global.nativeFlushQueueImmediate,global.nativeCallSyncHook分别对应上了原生的代码

3.2 moduleConfig

将各个RCTModuleData中的config数据保存为一个JSON字符串 :

{@"remoteModuleConfig": config}(这个config是个数组)

3.3 JSON数据注入

在以上两个步骤完成后,调用javaScriptExecutor的函数,将moduleconfig生成的JSON字符串注入到JSContext中:

[_javaScriptExecutor injectJSONText:configJSON asGlobalObjectNamed:@"__fbBatchedBridgeConfig" callback:onComplete];

这样,在JS环境中,就可以直接通过global.__fbBatchedBridgeConfig访问到config。具体过程点这里

4)执行加载成功的JSBundle

JSBundle被读入后,变成了NSData *sourceCode。然后,调用javaScriptExecutor的executeApplicationScript:sourceURL:onComplete.

该函数的实际过程是:

JSStringRef execJSString = JSStringCreateWithUTF8CString(ctx, (const char *)taggedScript.script.bytes);

JSEvaluateScript(ctx, execJSString, NULL, bundleURL, 0, &jsError);

JSStringRelease(ctx, execJSString);

在完成加载之后,RCTBatchedBridag会发送RCTJavaScriptDidLoadNotification 通知,

RCTRootView则会接受该通知,调用[selfrunApplication:bridge]; 该函数调用

[bridgeenqueueJSCall:@“AppRegistry"method:@“runApplication"args:@[moduleName, appParameters]completion:NULL];

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值