美文网首页
React native 源码之IOS初始化

React native 源码之IOS初始化

作者: milawoai | 来源:发表于2020-06-28 19:54 被阅读0次

    首先,我们要知道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];

    相关文章

      网友评论

          本文标题:React native 源码之IOS初始化

          本文链接:https://www.haomeiwen.com/subject/hxwhjttx.html