iOS Principle:ReactNative(上)

简介: iOS Principle:ReactNative(上)

方便记忆:


  • React原理:一套可以用简洁的语法高效绘制 DOM 的框架
  • React特点:
  • 简洁:不单单指它的 HTML 和 CSS 语法,更因为可以单用 JavaScript 构造页面
  • 高效:因为 React 独创了 Virtual DOM 机制,两大特征
  • 它存在于内存中的 JavaScript 对象,并且与 DOM 是对应关系
  • 使用高效的 DOM Diff 算法不需要对 DOM 进行重新绘制
  • React Native原理:通过 JS 对 OC 的 JavaScript Core 框架的交互来实现对原生的调用
  • rn 在 OC 和 JS 两端都保存了一份配置表,里面标记了所有 OC 暴露给 JS 的模块和方法 ,js对oc的调用通过block方式实现回调
  • AppDelegate初始化过程中创建bridge,内部通过setUp创建BatchedBridge来批量读取 JS 对 OC 的方法调用并通过JavaScriptExecutor执行 JS 代码
  • 创建 BatchedBridge 步骤
  • 读取 JS 源码:把 JSX 代码转成 JS 加载进内存中
  • 初始化模块信息:找到所有需要暴露给 JS 的类
  • 初始化 JS 代码的执行器:即 RCTJSCExecutor 对象
  • 生成模块列表并写入 JS 端:接受 ModuleName 并且生成模块信息
  • 执行 JavaScript 源码:通过RCTJSCExecutor执行代码,写入信息
  • 相互调用方法:
  • OC调用JS:OC会通过executeBlockOnJavaScriptQueue方法在单独的线程上运行 JS 代码
  • 处理参数:_executeJSCall:(NSString *)method方法
  • 实际调用:sendAppEventWithName和body方法
  • JS调用OC:JS 会解析出方法的类、方法和方法参数并放入到 MessageQueue 中,等待 OC 调用或超时发送
  • 使用RCT_EXPORT_METHOD 宏,用来注册模块表
  • JS中使用NativeModules.CryptoExport.注册方法调用
  • rn 的更新机制:React 状态机,不停地检查确认更新
  • 文本元素:ReactDOMTextComponent 比较替换文本元素
  • 基本元素:updateComponent方法分属性、节点替换基本元素
  • 自定义元素:_performComponentUpdate判断-先卸载再安装子节点

20180522更新:React Native 原理解析


准备工作,首先要有个解剖对象


从 HelloWord 看起,我们来分析RN的实现原理

import React, { Component } from 'react';
import { AppRegistry, Text } from 'react-native';
class HelloWorldApp extends Component {
  render() {
    return (
      <Text>Hello world!</Text>
    );
  }
}
// 注意,这里用引号括起来的'HelloWorldApp'必须和你init创建的项目名一致
AppRegistry.registerComponent('HelloWorldApp', () => HelloWorldApp);

可以创建一个新的项目

react-native init ProjectName

创建完成你可以手动打开项目,也可以在项目根目录执行

// 启动 iOS
react-native run-ios
// 启动 Android
react-native run-android

准备工作完成了


React 原理探究


首先我们聊聊 React,我们注意到这条数据源代码

return (
      <Text>Hello world!</Text>
);

“为什么 JavaScript 代码里面出现了 HTML 的语法?”

React Native 把一组相关的 HTML 标签,也就是 app 内的 UI 控件,封装进一个组件(Component)中,这种语法被称为 JSX,它是一种 JavaScript 语法拓展。

JSX 允许我们写 HTML 标签或 React 标签,它们终将被转换成原生的 JavaScript 并创建 DOM。

在 React 框架中,除了可以用 JavaScript 写 HTML 以外,我们甚至可以写 CSS。

总之 React 是一套可以用简洁的语法高效绘制 DOM 的框架

  • 简洁:不单单指它的 HTML 和 CSS 语法,更因为可以单用 JavaScript 构造页面;
  • 高效:因为 React 独创了 Virtual DOM 机制,Virtual DOM 有两大特征,一它存在于内存中的 JavaScript 对象,并且与 DOM 是一一对应的关系;二使用高效的 DOM Diff 算法不需要对 DOM 进行重新绘制。

当然,React 并不是前端开发的全部。从之前的描述也能看出,它专注于 UI 部分,对应到 MVC 结构中就是 View 层。

要想实现完整的 MVC 架构,还需要 Model 和 Controller 的结构。在前端开发时,我们可以采用 Flux 和 Redux(基于Flux) 架构,它们并非框架(Library),而是和 MVC 一样都是一种架构设计(Architecture)。


React Native 原理探究


谈谈 RN 的故事背景

而 React 在前端取得突破性成功以后,JavaScript 开始试图一统三端。

他们利用了移动平台能够运行 JavaScript (脚本语言)代码的能力,并且发挥了 JavaScript 不仅仅可以传递配置信息,还可以表达逻辑信息的优点。

最终,一个基于 JavaScript,具备动态配置能力,面向前端开发者的移动端开发框架 —— React Native


谈谈 RN 的原理

即使使用了 React Native,我们依然需要 UIKit 等框架,调用的是 Objective-C 代码,JavaScript 只是提供了配置信息和逻辑的处理结果。

而 JavaScript 是一种脚本语言,它不会经过编译、链接等操作,而是在运行时才动态的进行词法、语法分析,生成抽象语法树(AST)和字节码,然后由解释器负责执行或者使用 JIT 将字节码转化为机器码再执行。

苹果提供了一个叫做 JavaScript Core 的框架,这是一个 JavaScript 引擎。整个流程由 JavaScript 引擎负责完成。

JSContext *context = [[JSContext alloc] init];  
JSValue *jsVal = [context evaluateScript:@"21+7"];  
int iVal = [jsVal toInt32];

JavaScript 是一种单线程的语言,它不具备自运行的能力,因此总是被动调用。很多介绍 React Native 的文章都会提到 “JavaScript 线程” 的概念,实际上,它表示的是 Objective-C 创建了一个单独的线程,这个线程只用于执行 JavaScript 代码,而且 JavaScript 代码只会在这个线程中执行。


下面将 JavaScript 👉 OC

由于 JavaScript Core 是一个面向 Objective-C 的框架,在 Objective-C 这一端,我们对 JavaScript 上下文知根知底,可以很容易的获取到对象,方法等各种信息,当然也包括调用 JavaScript 函数。

真正复杂的问题在于,JavaScript 不知道 Objective-C 有哪些方法可以调用。

React Native 解决这个问题的方案是在 Objective-C 和 JavaScript 两端都保存了一份配置表,里面标记了所有 Objective-C 暴露给 JavaScript 的模块和方法。

这样,无论是哪一方调用另一方的方法,实际上传递的数据只有

  • ModuleId 类
  • MethodId 方法
  • Arguments  方法参数

当 Objective-C 接收到这三个值后,就可以通过 runtime 唯一确定要调用的是哪个函数,然后调用这个函数。

对于 Objective-C 来说,执行完 JavaScript 代码再执行 Objective-C 回调毫无难度,难点依然在于 JavaScript 代码调用 Objective-C 之后,如何在 Objective-C 的代码中,回调执行 JavaScript 代码。

目前 React Native 的做法是:在 JavaScript 调用 Objective-C 代码时,注册要回调的 Block,并且把 Block Id 作为参数发送给 Objective-C,Objective-C 收到参数时会创建 Block,调用完 Objective-C 函数后就会执行这个刚刚创建的 Block。

Objective-C 会向 Block 中传入参数和 Block Id,然后在 Block 内部调用 JavaScript 的方法,随后 JavaScript 查找到当时注册的 Block 并执行。

简单的表示就是:JS 👉 OC (Block 👉 JS)


继续看项目-初始化


除了 index.js 中的 JavaScript 代码,留给我们的还有 AppDelegate 中的入口方法:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  NSURL *jsCodeLocation;
  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"demo"
                                               initialProperties:nil
                                                   launchOptions:launchOptions];
  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  return YES;
}

实际我们操作的视图就是这个 RootView ,但是 RootView 是依托于 Bridge 对象,它是 Objective-C 与 JavaScript 交互的桥梁,后续的方法交互完全依赖于它,而整个初始化过程的最终目的其实也就是创建这个桥梁对象。

- (instancetype)initWithBundleURL:(NSURL *)bundleURL
                       moduleName:(NSString *)moduleName
                initialProperties:(NSDictionary *)initialProperties
                    launchOptions:(NSDictionary *)launchOptions {
  RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL
                                            moduleProvider:nil
                                             launchOptions:launchOptions];
  return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
}

初始化方法的核心是 setUp 方法,而 setUp 方法的主要任务则是创建 BatchedBridge。

- (void)setUp {
  RCT_PROFILE_BEGIN_EVENT(0, @"-[RCTBridge setUp]", nil);
  _performanceLogger = [RCTPerformanceLogger new];
  [_performanceLogger markStartForTag:RCTPLBridgeStartup];
  [_performanceLogger markStartForTag:RCTPLTTI];
  Class bridgeClass = self.bridgeClass;
  #if RCT_DEV
  RCTExecuteOnMainQueue(^{
    RCTRegisterReloadCommandListener(self);
  });
  #endif
  // Only update bundleURL from delegate if delegate bundleURL has changed
  NSURL *previousDelegateURL = _delegateBundleURL;
  _delegateBundleURL = [self.delegate sourceURLForBridge:self];
  if (_delegateBundleURL && ![_delegateBundleURL isEqual:previousDelegateURL]) {
    _bundleURL = _delegateBundleURL;
  }
  // Sanitize the bundle URL
  _bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString];
  self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
  [self.batchedBridge start];
  RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}

BatchedBridge 的作用是批量读取 JavaScript 对 Objective-C 的方法调用,同时它内部持有一个 JavaScriptExecutor,顾名思义,这个对象用来执行 JavaScript 代码。


创建 BatchedBridge 的关键是 start 方法,它可以分为五个步骤:

  • 读取 JavaScript 源码
  • 初始化模块信息
  • 初始化 JavaScript 代码的执行器,即 RCTJSCExecutor 对象
  • 生成模块列表并写入 JavaScript 端
  • 执行 JavaScript 源码

逐个分析上面每一步完成的操作:

1.读取JavaScript源码这一部分的具体代码实现没有太大的讨论意义。我们只要明白,JavaScript 的代码是在 Objective-C 提供的环境下运行的,所以第一步就是把 JavaScript 加载进内存中,对于一个空的项目来说,所有的 JavaScript 代码大约占用 1.5 Mb 的内存空间。

需要说明的是,在这一步中,JSX 代码已经被转化成原生的 JavaScript 代码。

2.初始化模块信息这一步在方法 initModulesWithDispatchGroup: 中实现,主要任务是找到所有需要暴露给 JavaScript 的类。

每一个需要暴露给 JavaScript 的类(也成为 Module,以下不作区分)都会标记一个宏:RCT_EXPORT_MODULE,这个宏的具体实现并不复杂

#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }

这样,这个类在 load 方法中就会调用 RCTRegisterModule 方法注册自己:

void RCTRegisterModule(Class moduleClass) {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    RCTModuleClasses = [NSMutableArray new];
  });
  [RCTModuleClasses addObject:moduleClass];
}

因此,React Native 可以通过 RCTModuleClasses 拿到所有暴露给 JavaScript 的类。下一步操作是遍历这个数组,然后生成 RCTModuleData 对象:

for (Class moduleClass in RCTGetModuleClasses()) {
    RCTModuleData *moduleData = [[RCTModuleData alloc]initWithModuleClass:moduleClass                                                                      bridge:self];
    [moduleClassesByID addObject:moduleClass];
    [moduleDataByID addObject:moduleData];
}

可以想见,RCTModuleData 对象是模块配置表的主要组成部分。如果把模块配置表想象成一个数组,那么每一个元素就是一个 RCTModuleData 对象。

这个对象保存了 Module 的名字,常量等基本信息,最重要的属性是一个数组,保存了所有需要暴露给 JavaScript 的方法。

暴露给 JavaScript 的方法需要用 RCT_EXPORT_METHOD 这个宏来标记,它的实现原理比较复杂,有兴趣的读者可以自行阅读。简单来说,它为函数名加上了 rct_export 前缀,再通过 runtime 获取类的函数列表,找出其中带有指定前缀的方法并放入数组中:

- (NSArray<id<RCTBridgeMethod>> *)methods{
    unsigned int methodCount;
    Method *methods = class_copyMethodList(object_getClass(_moduleClass), &methodCount); // 获取方法列表
    for (unsigned int i = 0; i < methodCount; i++) {
        RCTModuleMethod *moduleMethod = /* 创建 method */
        [_methods addObject:moduleMethod];
      }
    }
    return _methods;
}

因此 Objective-C 管理模块配置表的逻辑是:Bridge 持有一个数组,数组中保存了所有的模块的 RCTModuleData 对象,RCTModuleData又保存了类的方法、常亮、类名等信息。只要给定 ModuleId 和 MethodId 就可以唯一确定要调用的方法。

3.初始化JavaScript执行器(RCTJSCExecutor)通过查看源码可以看到,初始化 JavaScript 执行器的时候,会调用

+ (instancetype)initializedExecutorWithContextProvider:(RCTJSContextProvider *)JSContextProvider 
applicationScript:(NSData *)applicationScript 
sourceURL:(NSURL *)sourceURL 
JSContext:(JSContext **)JSContext 
error:(NSError **)error;

返回的 excuter 对象是已经被同步执行的

// 执行对应的方法
- (void)callFunctionOnModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args jsValueCallback:(RCTJavaScriptValueCallback)onComplete
{
  [self _callFunctionOnModule:module method:method arguments:args returnValue:NO unwrapResult:NO callback:onComplete];
}

这里需要关注 nativeRequireModuleConfig 和 nativeFlushQueueImmediate 这两个block。

在这两个 block 中会通过 bridge 调用 oc 的方法。

[self executeBlockOnJavaScriptQueue:^{
    if (!self.valid) {
      return;
    }
    JSContext *context = nil;
    if (self->_jscWrapper) {
      RCTAssert(self->_context != nil, @"If wrapper was pre-initialized, context should be too");
      context = self->_context.context;
    } else {
      [self->_performanceLogger markStartForTag:RCTPLJSCWrapperOpenLibrary];
      self->_jscWrapper = RCTJSCWrapperCreate(self->_useCustomJSCLibrary);
      [self->_performanceLogger markStopForTag:RCTPLJSCWrapperOpenLibrary];
      RCTAssert(self->_context == nil, @"Didn't expect to set up twice");
      context = [self->_jscWrapper->JSContext new];
      self->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context onThread:self->_javaScriptThread];
      [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification
                                                          object:context];
      configureCacheOnContext(context, self->_jscWrapper);
      installBasicSynchronousHooksOnContext(context);
    }
    __weak RCTJSCExecutor *weakSelf = self;
    context[@"nativeRequireModuleConfig"] = ^NSString *(NSString *moduleName) {
      RCTJSCExecutor *strongSelf = weakSelf;
      if (!strongSelf.valid) {
        return nil;
      }
      RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeRequireModuleConfig", nil);
      NSArray *config = [strongSelf->_bridge configForModuleName:moduleName];
      NSString *result = config ? RCTJSONStringify(config, NULL) : nil;
      RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,config", @{ @"moduleName": moduleName });
      return result;
    };
    context[@"nativeFlushQueueImmediate"] = ^(NSArray<NSArray *> *calls){
      RCTJSCExecutor *strongSelf = weakSelf;
      if (!strongSelf.valid || !calls) {
        return;
      }
      RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeFlushQueueImmediate", nil);
      [strongSelf->_bridge handleBuffer:calls batchEnded:NO];
      RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call", nil);
    };
#if RCT_PROFILE
    __weak RCTBridge *weakBridge = self->_bridge;
    context[@"nativeTraceBeginAsyncFlow"] = ^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) {
      if (RCTProfileIsProfiling()) {
        [weakBridge.flowIDMapLock lock];
        int64_t newCookie = [_RCTProfileBeginFlowEvent() longLongValue];
        CFDictionarySetValue(weakBridge.flowIDMap, (const void *)cookie, (const void *)newCookie);
        [weakBridge.flowIDMapLock unlock];
      }
    };
    context[@"nativeTraceEndAsyncFlow"] = ^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) {
      if (RCTProfileIsProfiling()) {
        [weakBridge.flowIDMapLock lock];
        int64_t newCookie = (int64_t)CFDictionaryGetValue(weakBridge.flowIDMap, (const void *)cookie);
        _RCTProfileEndFlowEvent(@(newCookie));
        CFDictionaryRemoveValue(weakBridge.flowIDMap, (const void *)cookie);
        [weakBridge.flowIDMapLock unlock];
      }
    };
#endif
#if RCT_DEV
    RCTInstallJSCProfiler(self->_bridge, context.JSGlobalContextRef);
    // Inject handler used by HMR
    context[@"nativeInjectHMRUpdate"] = ^(NSString *sourceCode, NSString *sourceCodeURL) {
      RCTJSCExecutor *strongSelf = weakSelf;
      if (!strongSelf.valid) {
        return;
      }
      RCTJSCWrapper *jscWrapper = strongSelf->_jscWrapper;
      JSStringRef execJSString = jscWrapper->JSStringCreateWithUTF8CString(sourceCode.UTF8String);
      JSStringRef jsURL = jscWrapper->JSStringCreateWithUTF8CString(sourceCodeURL.UTF8String);
      jscWrapper->JSEvaluateScript(strongSelf->_context.context.JSGlobalContextRef, execJSString, NULL, jsURL, 0, NULL);
      jscWrapper->JSStringRelease(jsURL);
      jscWrapper->JSStringRelease(execJSString);
    };
#endif
  }];
}


目录
相关文章
|
iOS开发
iOS Principle:CGAffineTransform
iOS Principle:CGAffineTransform
142 0
iOS Principle:CGAffineTransform
|
安全 Unix API
iOS Principle:CALayer(下)
iOS Principle:CALayer(下)
138 0
iOS Principle:CALayer(下)
|
iOS开发
iOS Principle:CALayer(中)
iOS Principle:CALayer(中)
120 0
iOS Principle:CALayer(中)
|
API C语言 iOS开发
iOS Principle:CALayer(上)
iOS Principle:CALayer(上)
142 0
iOS Principle:CALayer(上)
|
存储 缓存 iOS开发
iOS Principle:weak
iOS Principle:weak
118 0
iOS Principle:weak
|
存储 iOS开发
iOS Principle:Notification(下)
iOS Principle:Notification(下)
88 0
iOS Principle:Notification(下)
|
设计模式 iOS开发
iOS Principle:Notification(上)
iOS Principle:Notification(上)
101 0
iOS Principle:Notification(上)
|
Web App开发 JSON 移动开发
iOS Principle:ReactNative(下)
iOS Principle:ReactNative(下)
153 0
iOS Principle:ReactNative(下)
|
移动开发 前端开发 JavaScript
iOS Principle:ReactNative(中)
iOS Principle:ReactNative(中)
99 0
iOS Principle:ReactNative(中)
|
1月前
|
API 数据安全/隐私保护 iOS开发
利用uni-app 开发的iOS app 发布到App Store全流程
利用uni-app 开发的iOS app 发布到App Store全流程
88 3