美文网首页
React Native浅析

React Native浅析

作者: 三国韩信 | 来源:发表于2020-07-07 19:10 被阅读0次
    基本概念
    • 啥是ReacNative
      React Native:(简称RN)基于React开发的App,它的有点是可以跨平台、跳过App Store审核,远程更新代码,提高迭代频率和效率,既有Native的体验,又保留React的开发效率。相对的,缺点就是对于不熟悉前端开发的人员上手比较慢,不能真正意义上做到跨平台,使用后,对app体积增加。
    • React Native原理
      RN的底层简单的说是把React转换为原生API,那么在iOS和安卓平台是有区别的,毕竟苹果和安卓是2个完全不一的系统,所以React Native需要iOS,安卓都写,React Native底层解析原生API是分开实现的,iOS一套,安卓一套。

    如上所述RN在实现上iOS和安卓不太一样,下面的解析都是基于在iOS平台上的。毕竟我也不懂Android。

    React Native是如何做到JS和OC交互

    上面说到RN是把React的js代码转换为原生的api。也就是说要做到JS语言和OC能交互,要做到JS=>OC且OC=>JS。一门动态或者脚本语言要跟本地语言互通要具备如下几点:

    1. 本地语言有一个runtime机制来对对象的方法调用进行动态解析。
    2. 本地语言一定有一个脚本的解析引擎
    3. 建立一个脚本语言到本地语言的映射表,KEY是脚本语言认识的符号,VALUE是本地语言认识的符号。通过这个映射表来构建从脚本到本地的调用。

    那么以上的条件满足了么?刚好OC有runtime,也有JavaScriptCore.frame,完全能满足这些条件。

    ReactNative 结构图

    在原生的OC代码和React代码之间,有座桥梁链接在了一起(OCBridge和JSBridge)。
    这样的设计,原生的模块和JS模块就充分的解耦了,想要扩展就非常的方便。
    假如你发现现有RN框架有些功能做不到了?扩展写个原生代码模块,接入这个桥梁就行了。也就是说只要遵循RN的协议RCTBridgeModule去写的OC Module对象,使用RCT_EXPORT_MODULE()宏注册类,使用RCT_EXPORT_METHOD()宏注册方法,那么这个OC Module以及他的OC Method都会被JS与OC的ModuleConfig进行统一控制管理。

    一、RN 项目启动

    • 创建RCTBridge *bridge的时候,有不同的情况:
      1. 通过遵守RCTBridgeDelegate协议,实现协议方法sourceURLForBridge:来创建bridge。


        创建Bridge
        Bridge代理
      2. 在创建CTRootView的时候,指定URL的路径,通过创建RootView来创建bridge。


        image.png
      3. 指定URL路径的也分不同的情况。
        a、如上图,我们可以开启本地的服务,指定index路径。
        b、[[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 在指定已经编译好的放项目里的main.jsbundle。
        c、NSURL *jsCodeLocation = [CodePush bundleURL]; 还可以通过远程来动态下发jsbundle。

    注:1、当我们设置了通过codePush去远程下发,还没拿到新代码时,也会优先去读main.jsbundle的内容。
    2、当我们是通过指定访问index但是本地也没有开启服务时,也会自动去读main.jsbundle的内容。

    代码流程

    从上面可以看出,我们在创建rootView的时候,可以通过传入创建的不能动了URL来创建,此时,在rootView内部会自动创建一个bridge。如果项目里的rootView有多个的话,那么就会出现多个bridge的情况。当重复进入react-native页面、退出react-native页面的操作,RCTBridge对象会被重复创建、销毁。这样会照成很大的内存开销。所以原生中会访问多个RN页面,建议把bridge的创建单例化,多个RCTRootView可共用一个RCTBridge。

    @interface  HJBridgeManager : NSObject
    // 全局唯一的bridge
    @property (nonatomic, readonly, strong) RCTBridge *bridge;
    + (instancetype)shareInstance;
    @end
    
    @implementation HJBridgeManager
    
    static HJBridgeManager *_instance = nil;
    + (instancetype)shareInstance{
        if (_instance == nil) {
            _instance = [[self alloc] init];
        }
        return _instance;
    }
    
    + (instancetype)allocWithZone:(struct _NSZone *)zone{
        if (_instance == nil) {
            static dispatch_once_t onceToken;
            dispatch_once(&onceToken, ^{
                _instance = [super allocWithZone:zone];
            });
        }
        return _instance;
    }
    
    -(instancetype)init{
        if (self = [super init]) {
           NSURL *jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
           //NSURL *jsCodeLocation = [CodePush bundleURL];
           //NSURL* jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
            
            _bridge = [[RCTBridge alloc]initWithBundleURL:jsCodeLocation moduleProvider:nil launchOptions:nil];
        }
        return self;
    }
    @end
    

    二、创建Bridge
    1、bridge的初始化函数中,会调用setUp 方法,setUp方法最重要的就是去初始化batchedBridge。batchedBridge是RCTCxxBridge的实例,而RCTCxxBridge又是RCTBridge的子类。RCTBridge负责对外暴露接口,而正在的实现都是在RCTCxxBridge中。

    // 这是setUp方法中最重要的代码,去初始化batchedBridge,调用它的start方法。
    self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
    [self.batchedBridge start];
    

    2、RCTCxxBridge的start方法

      [[NSNotificationCenter defaultCenter]
        postNotificationName:RCTJavaScriptWillStartLoadingNotification
        object:_parentBridge userInfo:@{@"bridge": self}];
    
      // Set up the JS thread early
      _jsThread = [[NSThread alloc] initWithTarget:[self class]
                                          selector:@selector(runRunLoop)
                                            object:nil];
      _jsThread.name = RCTJSThreadName;
      _jsThread.qualityOfService = NSOperationQualityOfServiceUserInteractive;
    #if RCT_DEBUG
      _jsThread.stackSize *= 2;
    #endif
      [_jsThread start];
    

    首先会触发一个RCTJavaScriptWillStartLoadingNotification通知,从通知命名一看便知这里是告诉我们即将要加载JavaScript代码。然后去创建了一个名称叫“com.facebook.react.JavaScript”的线程并启动线程的runloop(线程保活)。通过线程的名称很容易明白它是用来干啥的。这个线程是一直存在的。

      [_performanceLogger markStartForTag:RCTPLNativeModuleInit];
      [self registerExtraModules];
      // Initialize all native modules that cannot be loaded lazily
      (void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
      [self registerExtraLazyModules];
      [_performanceLogger markStopForTag:RCTPLNativeModuleInit];
    

    这里主要是操作我们的ExtraModules。把我们初试话bridge时传入的model数组里的modules包装成RCTModuleData的对象,并添加到bride的_moduleDataByName、_moduleClassesByID和_moduleDataByID中。

    // 在RCTModuleData类中去收集modules类中通过RCT_EXPORT_METHOD这个宏定义的的方法
    - (void) calculateMethods
    {
        if (_methods && _methodsByName) {
          return;
        }
        NSMutableArray<id<RCTBridgeMethod>> *moduleMethods = [NSMutableArray new];
        NSMutableDictionary<NSString *, id<RCTBridgeMethod>> *moduleMethodsByName = [NSMutableDictionary new];
        if ([_moduleClass instancesRespondToSelector:@selector(methodsToExport)]) {
            [moduleMethods addObjectsFromArray:[self.instance methodsToExport]];
        }
        unsigned int methodCount;
        Class cls = _moduleClass;
        while (cls && cls != [NSObject class] && cls != [NSProxy class]) {
            Method *methods = class_copyMethodList(object_getClass(cls), &methodCount);
            for (unsigned int i = 0; i < methodCount; i++) {
                Method method = methods[i];
                SEL selector = method_getName(method);
    // 通过宏导出的方法的方法名会被加了__rct_export__前缀,这里通过这个前缀来过滤那些导出的方法。
                if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
                    IMP imp = method_getImplementation(method);
                    auto exportedMethod = ((const RCTMethodInfo *(*)(id, SEL))imp)(_moduleClass, selector);
                    id<RCTBridgeMethod> moduleMethod = [[RCTModuleMethod alloc] initWithExportedMethod:exportedMethod moduleClass:_moduleClass];
                    NSString *str = [NSString stringWithUTF8String:moduleMethod.JSMethodName];
                    [moduleMethodsByName setValue:moduleMethod forKey:str];
                    [moduleMethods addObject:moduleMethod];
                }
            }
            free(methods);
            cls = class_getSuperclass(cls);
        }
        _methods = [moduleMethods copy];
        _methodsByName = [moduleMethodsByName copy];
    }
    

    简单的可以理解为我们定义的modules类被包装成了RCTModuleData。类的信息都放在了RCTModuleData中,包括所有导出到js的方法。(methods属性存放所有导出方法的数组,每个方法包装成一个RCTModuleMethod对象)

    // Dispatch the instance initialization as soon as the initial module metadata has
      // been collected (see initModules)
      dispatch_group_enter(prepareBridge);
      [self ensureOnJavaScriptThread:^{
        [weakSelf _initializeBridge:executorFactory];
        dispatch_group_leave(prepareBridge);
      }];
    
      // Load the source asynchronously, then store it for later execution.
      dispatch_group_enter(prepareBridge);
      __block NSData *sourceCode;
      [self loadSource:^(NSError *error, RCTSource *source) {
        if (error) {
          [weakSelf handleError:error];
        }
    
        sourceCode = source.data;
        dispatch_group_leave(prepareBridge);
      } onProgress:^(RCTLoadingProgress *progressData) {
    #if RCT_DEV && __has_include(<React/RCTDevLoadingView.h>)
        // Note: RCTDevLoadingView should have been loaded at this point, so no need to allow lazy loading.
        RCTDevLoadingView *loadingView = [weakSelf moduleForName:RCTBridgeModuleNameForClass([RCTDevLoadingView class])
                                           lazilyLoadIfNecessary:NO];
        [loadingView updateProgress:progressData];
    #endif
      }];
    
      // Wait for both the modules and source code to have finished loading
      dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
        RCTCxxBridge *strongSelf = weakSelf;
        if (sourceCode && strongSelf.loading) {
          [strongSelf executeSourceCode:sourceCode sync:NO];
        }
      });
    

    这里通过dispatch_group_t来异步的去子线程操作,主要有2个。一个是去上面创建的_jsThread子线程里执行block的内容,主要是_reactInstance的初始化。
    另一个线程主要是loadSource方法来加载JavaScript代码。在loadSource里会先去判断是否有自己实现了代理方法,如果有则调用开发者实现的代理方法,如果没有则会通过RCTJavaScriptLoader 类去调用loadBundleAtURL 方法来加载js代码。在loadBundleAtURL 方法中会先用同步的方法去加载,如果失败,会调用异步加载的方法,最后加载得到的是一个NSData对象。

    加载JavaScript代码流程
     // Wait for both the modules and source code to have finished loading
      dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
        RCTCxxBridge *strongSelf = weakSelf;
        if (sourceCode && strongSelf.loading) {
          [strongSelf executeSourceCode:sourceCode sync:NO];
        }
      });
    

    当上面的2个线程都结束了,就会通过dispatch_group_notify回调到下面去执行[strongSelf executeSourceCode:sourceCode sync:NO]; 就是去执行JS代码。
    至此,RCTBride的初始化就结束了。

    三、创建RCTRootView

    - (instancetype)initWithBridge:(RCTBridge *)bridge
                        moduleName:(NSString *)moduleName
                 initialProperties:(NSDictionary *)initialProperties
    {
    if (self = [super initWithFrame:CGRectZero]) {
        self.backgroundColor = [UIColor whiteColor];
        _bridge = bridge;
        _moduleName = moduleName;
        _appProperties = [initialProperties copy];
        _loadingViewFadeDelay = 0.25;
        _loadingViewFadeDuration = 0.25;
        _sizeFlexibility = RCTRootViewSizeFlexibilityNone;
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(bridgeDidReload)
    name:RCTJavaScriptWillStartLoadingNotification object:_bridge];
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(javaScriptDidLoad:)
                                                     name:RCTJavaScriptDidLoadNotification
                                                   object:_bridge];
         [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(hideLoadingView)
                                                     name:RCTContentDidAppearNotification
                                                 object:self];
    
        ......
    
          // Immediately schedule the application to be started.
          // (Sometimes actual `_bridge` is already batched bridge here.)
        [self bundleFinishedLoading:([_bridge batchedBridge] ?: _bridge)];
      }  
        return self;
    }
    
    - (void)bundleFinishedLoading:(RCTBridge *)bridge{
      RCTAssert(bridge != nil, @"Bridge cannot be nil");
      if (!bridge.valid) {
        return;
      }
      [_contentView removeFromSuperview];
      _contentView = [[RCTRootContentView alloc] initWithFrame:self.bounds
                                                        bridge:bridge
                                                      reactTag:self.reactTag
                                                sizeFlexiblity:_sizeFlexibility];
      // 主要的执行代码
      [self runApplication:bridge];
      _contentView.passThroughTouches = _passThroughTouches;
      [self insertSubview:_contentView atIndex:0];
    
      if (_sizeFlexibility == RCTRootViewSizeFlexibilityNone) {
        self.intrinsicContentSize = self.bounds.size;
      }
    }
    
    - (void)runApplication:(RCTBridge *)bridge
    {
      NSString *moduleName = _moduleName ?: @"";
      NSDictionary *appParameters = @{
        @"rootTag": _contentView.reactTag,
        @"initialProps": _appProperties ?: @{},
      };
    
      RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters);
      [bridge enqueueJSCall:@"AppRegistry"
                     method:@"runApplication"
                       args:@[moduleName, appParameters]
                 completion:NULL];
    }
    

    从上面的 代码可以看出,rootView的初始化主要是去执行runApplication方法,这个方法在jsTread线程里去调用js的函数。另外就是去初始化RCTRootContentView。这个类有个touchHandler属性,继承于UIGestureRecognizer用于处理页面的手势的响应。

    四、React Native事件处理流程(iOS)
    1.在创建RCTRootContentView的时候,内部会创建RCTTouchHandler
    RCTTouchHandler:继承UIGestureRecognizer,也就是它就是一个手势
    并且它会作为RCTRootContentView的手势,这样点击RCTRootContentView,就会触发RCTTouchHandler
    RCTTouchHandler:内部实现了touchBegin等触摸方法,用来处理触摸事件
    2.在创建RCTTouchHandler的时候,内部会创建RCTEventDispatcher
    RCTEventDispatcher:用来把事件处理传递给JS的方法处理,也就是当UI界面产生事件,就会执行JS的代码处理。
    3.通过RCTRootContentView截获点击事件
    产生事件就会去触犯RCTRootContentView中的RCTTouchHandler对象。
    4.当产生事件的时候,会执行[RCTTouchHandler touchBegin]
    5.RCTTouchHandler的touch方法,会执行[RCTTouchHandler _updateAndDispatchTouches:eventName:]
    内部会创建RCTTouchEvent事件对象
    6.[RCTEventDispatcher sendEvent:event] -> 让事件分发对象调用发送事件对象
    内部会把事件保存到_eventQueue(事件队列中)
    7.[RCTEventDispatcher flushEventsQueue] -> 让事件分发对象冲刷事件队列,就是获取事件队列中所有事件执行
    8.[RCTEventDispatcher dispatchEvent:event] -> 遍历事件队列,一个一个分发事件
    分发事件的本质:就是去执行JS的代码,相应事件。
    9.[RCTBatchedBridge enqueueJSCall:[[event class] moduleDotMethod] args:[event arguments]]; -> 让桥架对象调用JS处理事件
    本质:就是产生事件调用JS代码
    10.这样就能完成把UI事件交给JS代码相应

    总结图

    引用文章:
    ReactNative源码分析
    React Native在美团外卖客户端的实践
    ReactNative iOS源码解析(二)

    相关文章

      网友评论

          本文标题:React Native浅析

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