React Native浅析
基本概念
- 啥是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。一门动态或者脚本语言要跟本地语言互通要具备如下几点:
- 本地语言有一个runtime机制来对对象的方法调用进行动态解析。
- 本地语言一定有一个脚本的解析引擎
- 建立一个脚本语言到本地语言的映射表,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的时候,有不同的情况:
-
通过遵守RCTBridgeDelegate协议,实现协议方法sourceURLForBridge:来创建bridge。
创建Bridge
Bridge代理 -
在创建CTRootView的时候,指定URL的路径,通过创建RootView来创建bridge。
image.png -
指定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对象。
// 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源码解析(二)