React native源自React,React 是一套可以用简洁的语法高效绘制Dom的框架。React中需要使用JSX语法,JSX是对JavaScript的扩展。JSX可以将CSS,HTML,表达式进行一起书写,简化了html代码书写的形式。
Virtual Dom
JSX 经过 babel 编译为 React.createElement() 的形式,其返回结果就是 Virtual DOM,最后通过 ReactDOM.render() 将 Virtual DOM 转化为真实的 DOM 展现在界面上。
虚拟DOM是React的优势,具有批处理和高效的Diff算法。这让我们可以无需担心性能问题而”毫无顾忌”的随时“刷新”整个页面,由虚拟 DOM来确保只对界面上真正变化的部分进行实际的DOM操作。
React native
React 在前端取得成功之后,希望在移动端开发统一Android和iOS,于是React native随之诞生。React native可以通过JSX语法更快的实现UI布局,通过JavaScriptCore与native之间进行交互。
JavaScript 是一种单线程的语言,它不具备自运行的能力,总是被动调用。React Native执行环境是是 Objective-C 创建了一个单独的线程,这个线程只用于执行 JavaScript 代码,而且 JavaScript 代码只会在这个线程中执行。
即使使用了React Native,依然需要 UIKit 等框架,调用的是 Objective-C 代码。JavaScript 只是辅助,提供了一个简化两端UI重复开发一个工作,对于Native而言只是锦上添花,而非雪中送炭。单纯想通过React native放弃native端目前是不太现实的。
React native 加载流程
JavaScript Core 是一个面向 Objective-C 的框架,在 Objective-C 可以通过JSContext获取到对象,方法等各种信息,调用执行JavaScript 函数。
React Native 解决这个问题的方案是在 Objective-C 和 JavaScript 两端都保存了一份配置表,里面标记了所有 Objective-C 暴露给 JavaScript 的模块和方法。这样,无论是哪一方调用另一方的方法,实际上传递的数据只有 ModuleId、MethodId 和 Arguments 这三个元素,它们分别表示类、方法和方法参数,当 Objective-C 接收到这三个值后,就可以通过 runtime 唯一确定要调用的是哪个函数,然后调用这个函数。
React native视图展示通过RCTRootView来展示,RCTRootView需要一个实例化的RCTBridge.
rctBridge = [[RCTBridge alloc] initWithBundleURL:rnCodeLocation
moduleProvider:nil
launchOptions:launchOptions];
RCTBridge是 Objective-C 与 JavaScript 交互的桥梁,后续的方法交互完全依赖于它,而整个初始化过程的最终目的其实也就是创建这个桥梁对象。
RCTBridge通过初始化方法setUp来生成RCTBatchedBridge。
RCTBatchedBridge 的作用是批量读取 JavaScript 对 Objective-C 的方法调用,同时它内部持有一个 JavaScriptExecutor,这个对象用来执行 JavaScript 代码。
Class batchedBridgeClass = objc_lookUpClass("RCTBatchedBridge");
Class cxxBridgeClass = objc_lookUpClass("RCTCxxBridge");
Class implClass = nil;
if ([self.delegate respondsToSelector:@selector(shouldBridgeUseCxxBridge:)]) {
if ([self.delegate shouldBridgeUseCxxBridge:self]) {
implClass = cxxBridgeClass;
} else {
implClass = batchedBridgeClass;
}
} else if (cxxBridgeClass != nil) {
implClass = cxxBridgeClass;
} else if (batchedBridgeClass != nil) {
implClass = batchedBridgeClass;
}
BatchedBridge 的关键是 start 方法分为五个步骤:
- 加载 JavaScript 源码
- 加载所有不能被懒加载的native modules
- 初始化RCTJSCExecutor 对象,执行JavaScript代码
- 生成模块列表并写入 JavaScript 端
- 执行 JavaScript 源码
加载JavaScript源码
__weak RCTBatchedBridge *weakSelf = self;
__block NSData *sourceCode;
[self loadSource:^(NSError *error, NSData *source, __unused int64_t sourceLength) {
if (error) {
RCTLogWarn(@"Failed to load source: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
});
}
sourceCode = source;
dispatch_group_leave(initModulesAndLoadSource);
} onProgress:^(RCTLoadingProgress *progressData) {
#if RCT_DEV && __has_include("RCTDevLoadingView.h")
RCTDevLoadingView *loadingView = [weakSelf moduleForClass:[RCTDevLoadingView class]];
[loadingView updateProgress:progressData];
#endif
}];
加载native modules
注册Module
RCT_EXPORT_MODULE 可以通过RCTRegisterModule注册Module.
@protocol RCTBridgeModule <NSObject>
/**
* Place this macro in your class implementation to automatically register
* your module with the bridge when it loads. The optional js_name argument
* will be used as the JS module name. If omitted, the JS module name will
* match the Objective-C class name.
*/
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
// Implemented by RCT_EXPORT_MODULE
+ (NSString *)moduleName;
@optional
NSMutableArray<Class> *moduleClassesByID = [NSMutableArray new];
NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new];
NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new];
// Set up moduleData for pre-initialized module instances
RCT_PROFILE_BEGIN_EVENT(0, @"extraModules", nil);
for (id<RCTBridgeModule> module in extraModules) {
Class moduleClass = [module class];
NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
if (RCT_DEBUG) {
// Check for name collisions between preregistered modules
RCTModuleData *moduleData = moduleDataByName[moduleName];
if (moduleData) {
RCTLogError(@"Attempted to register RCTBridgeModule class %@ for the "
"name '%@', but name was already registered by class %@",
moduleClass, moduleName, moduleData.moduleClass);
continue;
}
}
// Instantiate moduleData container
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
bridge:self];
moduleDataByName[moduleName] = moduleData;
[moduleClassesByID addObject:moduleClass];
[moduleDataByID addObject:moduleData];
// Set executor instance
if (moduleClass == self.executorClass) {
_javaScriptExecutor = (id<RCTJavaScriptExecutor>)module;
}
}
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
// The executor is a bridge module, but we want it to be instantiated before
// any other module has access to the bridge, in case they need the JS thread.
// TODO: once we have more fine-grained control of init (t11106126) we can
// probably just replace this with [self moduleForClass:self.executorClass]
RCT_PROFILE_BEGIN_EVENT(0, @"JavaScriptExecutor", nil);
if (!_javaScriptExecutor) {
id<RCTJavaScriptExecutor> executorModule = [self.executorClass new];
RCTModuleData *moduleData = [[RCTModuleData alloc] initWithModuleInstance:executorModule
bridge:self];
moduleDataByName[moduleData.name] = moduleData;
[moduleClassesByID addObject:self.executorClass];
[moduleDataByID addObject:moduleData];
RCTModuleData 模型
RCTModuleData 保存需要导出的常量和实例方法.
/**
* Returns the module methods. Note that this will gather the methods the first
* time it is called and then memoize the results.
*/
@property (nonatomic, copy, readonly) NSArray<id<RCTBridgeMethod>> *methods;
/**
* Returns the module's constants, if it exports any
*/
@property (nonatomic, copy, readonly) NSDictionary<NSString *, id> *exportedConstants;
RCTModuleData暴露给 JavaScript 的方法需要用 RCT_EXPORT_METHOD 这个宏来标记,为函数名加上了 rct_export 前缀,再通过 runtime 获取类的函数列表,找出其中带有指定前缀的方法并放入数组中:
- (NSArray<id<RCTBridgeMethod>> *)methods
{
if (!_methods) {
NSMutableArray<id<RCTBridgeMethod>> *moduleMethods = [NSMutableArray 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);
if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
IMP imp = method_getImplementation(method);
NSArray *entries =
((NSArray *(*)(id, SEL))imp)(_moduleClass, selector);
id<RCTBridgeMethod> moduleMethod =
[[RCTModuleMethod alloc] initWithMethodSignature:entries[1]
JSMethodName:entries[0]
isSync:((NSNumber *)entries[2]).boolValue
moduleClass:_moduleClass];
[moduleMethods addObject:moduleMethod];
}
}
free(methods);
cls = class_getSuperclass(cls);
}
_methods = [moduleMethods copy];
}
return _methods;
}
Objective-C 中Bridge 持有一个数组,数组中保存了所有的模块的 RCTModuleData 对象。只要给定 ModuleId 和 MethodId 就可以唯一确定要调用的方法。
初始化JSExecutor
dispatch_group_notify(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
// We're not waiting for this to complete to leave dispatch group, since
// injectJSONConfiguration and executeSourceCode will schedule operations
// on the same queue anyway.
[performanceLogger markStartForTag:RCTPLNativeModuleInjectConfig];
[weakSelf injectJSONConfiguration:config onComplete:^(NSError *error) {
[performanceLogger markStopForTag:RCTPLNativeModuleInjectConfig];
if (error) {
RCTLogWarn(@"Failed to inject config: %@", error);
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf stopLoadingWithError:error];
});
}
}];
dispatch_group_leave(initModulesAndLoadSource);
});
- (void)setUp
{
#if RCT_PROFILE
#ifndef __clang_analyzer__
_bridge.flowIDMap = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
#endif
_bridge.flowIDMapLock = [NSLock new];
for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(toggleProfilingFlag:)
name:event
object:nil];
}
#endif
[self executeBlockOnJavaScriptQueue:^{
if (!self.valid) {
return;
}
JSGlobalContextRef contextRef = nullptr;
JSContext *context = nil;
if (self->_context) {
context = self->_context.context;
contextRef = context.JSGlobalContextRef;
} else {
if (self->_useCustomJSCLibrary) {
JSC_configureJSCForIOS(true, RCTJSONStringify(@{
@"StartSamplingProfilerOnInit": @(self->_bridge.devSettings.startSamplingProfilerOnLaunch)
}, NULL).UTF8String);
}
contextRef = JSC_JSGlobalContextCreateInGroup(self->_useCustomJSCLibrary, nullptr, nullptr);
context = [JSC_JSContext(contextRef) contextWithJSGlobalContextRef:contextRef];
// We release the global context reference here to balance retainCount after JSGlobalContextCreateInGroup.
// The global context _is not_ going to be released since the JSContext keeps the strong reference to it.
JSC_JSGlobalContextRelease(contextRef);
self->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context onThread:self->_javaScriptThread];
[[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification
object:context];
installBasicSynchronousHooksOnContext(context);
}
RCTFBQuickPerformanceLoggerConfigureHooks(context.JSGlobalContextRef);
__weak RCTJSCExecutor *weakSelf = self;
context[@"nativeRequireModuleConfig"] = ^NSArray *(NSString *moduleName) {
RCTJSCExecutor *strongSelf = weakSelf;
if (!strongSelf.valid) {
return nil;
}
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeRequireModuleConfig", @{ @"moduleName": moduleName });
NSArray *result = [strongSelf->_bridge configForModuleName:moduleName];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,config");
return RCTNullIfNil(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");
};
context[@"nativeCallSyncHook"] = ^id(NSUInteger module, NSUInteger method, NSArray *args) {
RCTJSCExecutor *strongSelf = weakSelf;
if (!strongSelf.valid) {
return nil;
}
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeCallSyncHook", nil);
id result = [strongSelf->_bridge callNativeModule:module method:method params:args];
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,config");
return result;
};
#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];
NSUInteger newCookie = _RCTProfileBeginFlowEvent();
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];
NSUInteger newCookie = (NSUInteger)CFDictionaryGetValue(weakBridge.flowIDMap, (const void *)cookie);
_RCTProfileEndFlowEvent(newCookie);
CFDictionaryRemoveValue(weakBridge.flowIDMap, (const void *)cookie);
[weakBridge.flowIDMapLock unlock];
}
};
// Add toggles for JSC's sampling profiler, if the profiler is enabled
if (JSC_JSSamplingProfilerEnabled(context.JSGlobalContextRef)) {
// Mark this thread as the main JS thread before starting profiling.
JSC_JSStartSamplingProfilingOnMainJSCThread(context.JSGlobalContextRef);
__weak JSContext *weakContext = self->_context.context;
#if __has_include("RCTDevMenu.h")
// Allow to toggle the sampling profiler through RN's dev menu
[self->_bridge.devMenu addItem:[RCTDevMenuItem buttonItemWithTitle:@"Start / Stop JS Sampling Profiler" handler:^{
RCTJSCExecutor *strongSelf = weakSelf;
if (!strongSelf.valid || !weakContext) {
return;
}
[weakSelf.bridge.devSettings toggleJSCSamplingProfiler];
}]];
#endif
// Allow for the profiler to be poked from JS code as well
// (see SamplingProfiler.js for an example of how it could be used with the JSCSamplingProfiler module).
context[@"pokeSamplingProfiler"] = ^NSDictionary *() {
if (!weakContext) {
return @{};
}
JSGlobalContextRef ctx = weakContext.JSGlobalContextRef;
JSValueRef result = JSC_JSPokeSamplingProfiler(ctx);
return [[JSC_JSValue(ctx) valueWithJSValueRef:result inContext:weakContext] toObject];
};
}
#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;
}
JSGlobalContextRef ctx = strongSelf->_context.context.JSGlobalContextRef;
JSStringRef execJSString = JSC_JSStringCreateWithUTF8CString(ctx, sourceCode.UTF8String);
JSStringRef jsURL = JSC_JSStringCreateWithUTF8CString(ctx, sourceCodeURL.UTF8String);
JSC_JSEvaluateScript(ctx, execJSString, NULL, jsURL, 0, NULL);
JSC_JSStringRelease(ctx, jsURL);
JSC_JSStringRelease(ctx, execJSString);
};
#endif
}];
}
Native代码注册到JavaScript端
- (NSString *)moduleConfig
{
NSMutableArray<NSArray *> *config = [NSMutableArray new];
for (RCTModuleData *moduleData in _moduleDataByID) {
if (self.executorClass == [RCTJSCExecutor class]) {
[config addObject:@[moduleData.name]];
} else {
[config addObject:RCTNullIfNil(moduleData.config)];
}
}
return RCTJSONStringify(@{
@"remoteModuleConfig": config,
}, NULL);
}
Objective-C 把 config 字符串设置成 JavaScript 中名为__fbBatchedBridgeConfig的全局变量。
[_javaScriptExecutor injectJSONText:configJSON
asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
callback:onComplete];
执行JavaScript代码
- (void)enqueueApplicationScript:(NSData *)script
url:(NSURL *)url
onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
RCTAssert(onComplete != nil, @"onComplete block passed in should be non-nil");
RCTProfileBeginFlowEvent();
[_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {
RCTProfileEndFlowEvent();
RCTAssertJSThread();
if (scriptLoadError) {
onComplete(scriptLoadError);
return;
}
RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"FetchApplicationScriptCallbacks", nil);
[self->_javaScriptExecutor flushedQueue:^(id json, NSError *error)
{
RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,init");
[self handleBuffer:json batchEnded:YES];
onComplete(error);
}];
}];
}
Objective-C 与 JavaScript交互
Objective-C 调用 JavaScript
Objective-C开辟一个单独的线程运行JavaScript代码:
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
{
if ([NSThread currentThread] != _javaScriptThread) {
[self performSelector:@selector(executeBlockOnJavaScriptQueue:)
onThread:_javaScriptThread withObject:block waitUntilDone:NO];
} else {
block();
}
}
调用JavaScript可以直接发送事件:
/**
* Send an event that does not relate to a specific view, e.g. a navigation
* or data update notification.
*/
- (void)sendEventWithName:(NSString *)name body:(id)body;
JavaScript调用Native
在调用 Objective-C 代码时,JavaScript 会解析出方法的 moduleID、methodID 和 params 并放入到 MessageQueue 中,等待 Objective-C 主动拿走,或者超时后主动发送给 Objective-C。
- (id)callNativeModule:(NSUInteger)moduleID
method:(NSUInteger)methodID
params:(NSArray *)params
{
if (!_valid) {
return nil;
}
RCTModuleData *moduleData = _moduleDataByID[moduleID];
if (RCT_DEBUG && !moduleData) {
RCTLogError(@"No module found for id '%zd'", moduleID);
return nil;
}
id<RCTBridgeMethod> method = moduleData.methods[methodID];
if (RCT_DEBUG && !method) {
RCTLogError(@"Unknown methodID: %zd for module: %zd (%@)", methodID, moduleID, moduleData.name);
return nil;
}
@try {
return [method invokeWithBridge:self module:moduleData.instance arguments:params];
}
@catch (NSException *exception) {
// Pass on JS exceptions
if ([exception.name hasPrefix:RCTFatalExceptionName]) {
@throw exception;
}
NSString *message = [NSString stringWithFormat:
@"Exception '%@' was thrown while invoking %@ on target %@ with params %@",
exception, method.JSMethodName, moduleData.name, params];
RCTFatal(RCTErrorWithMessage(message));
return nil;
}
}
优势 / 劣势
优势:
对于开发者而言,移动开发者有机会熟悉前端开发模式,前端能了解native开发流程;
能够利用 JavaScript 动态更新的特性,快速迭代。
劣势:
1.无法兼容Android和iOS两个版本,官方版本的组件中有Android和iOS组建的区分;
2.不能做到完全屏蔽 iOS 端或 Android 的细节,前端开发者必须对原生平台有所了解。
3.由于 Objective-C 与 JavaScript 之间切换存在固定的时间开销,所以性能必定不及原生。
参考连接: