2016 年 4 月 21 日,阿里巴巴在 Qcon 大会上宣布跨平台移动开发工具 Weex 开放内测邀请。Weex 能够完美兼顾性能与动态性,让移动开发者通过简捷的前端语法写出 Native 级别的性能体验,并支持 iOS、安卓、YunOS 及 Web 等多端部署。
近一年来,ReactNative 和 Weex 这些跨平台技术对 Native 开发者来说,冲击是巨大的。Native 在开发 App 的时候存在一些弊端,比如客户端需要频繁更新,iOS 更新时间还要受到审核的牵制;iOS、Android 和前端同时开发同一个需求,在人员成本上消耗大;Hybrid 的性能和 Native 相比又差了一点。
ReactNative 和 Weex 的出现,就是为了解决这些痛点的。
从 4 月 21 号宣布内测以后,短短两周就有超过 5000 名开发者申请。
2016 年 6 月 30 日阿里巴巴正式宣布 Weex 开源。号称可以用 Web 方式,开发 Native 级性能体验的亿级应用匠心打造跨平台移动开发工具 Weex 在开源首日就登上 Github 趋势榜首位,截止目前为止,Weex 在 GitHub 上的 Star 数已经到达了 13393 了。成为中国 2016 年在 Github 上最热门的开源项目之一。
Weex 从出生那天起,仿佛就是和 ReactNative 是 "一对"。
ReactNative 宣称 "Learn once, write anywhere",而 Weex 宣称 "Write Once, Run Everywhere"。Weex 从出生那天起,就被给予了一统三端的厚望。ReactNative 可以支持 iOS、Android,而 Weex 可以支持 iOS、Android、HTML5。一统三端就解决了前言里面说的第二个痛点,同时开发浪费人员成本的问题。
Native 移动开发者只需要在本地导入 Weex 的 SDK,就可以通过 HTML/CSS/JavaScript 网页的这套编程语言来开发 Native 级别的 Weex 界面。这意味着可以直接用现有 Web 开发的编辑器和 IDE 的代码补全、提示、检查等功能。从而也给前端人员开发 Native 端,较低的开发成本和学习成本。
Weex 是一种轻量级、可扩展、高性能框架。集成也很方便,可以直接在 HTML5 页面嵌入,也可嵌在原生 UI 中。由于和 ReactNative 一样,都会调用 Native 端的原生控件,所以在性能上比 Hybrid 高出一个层次。这就解决了前言里面所说的第三个痛点,性能问题。
Weex 非常轻量,体积小巧,语法简单,方便接入和上手。ReactNative 官方只允许将 ReactNative 基础 js 库和业务 JS 一起打成一个 JS bundle,没有提供分包的功能,所以如果想节约流量就必须制作分包打包工具。而 Weex 默认打的 JS bundle 只包含业务 JS 代码,体积小很多,基础 JS 库包含在 Weex SDK 中,这一点 Weex 与 Facebook 的 React Native 和微软的 Cordova 相比,Weex 更加轻量,体积小巧。把 Weex 生成的 JS bundle 轻松部署到服务器端,然后 Push 到客户端,或者客户端请求新的资源即可完成发布。如此快速的迭代就解决了前言里面说的第一个痛点,发布无法控制时间,
Weex 中 Native 组件和 API 都可以横向扩展,业务方可去中心化横向灵活化定制组件和功能模块。并且还可以直接复用 Web 前端的工程化管理和监控性能等工具。
知乎上有一个关于 Weex 和 ReactNative 很好的对比文章 weex&ReactNative 对比 ,推荐大家阅读。
Weex 在 2017 年 2 月 17 日正式发布 v0.10.0 ,这个里程碑的版本开始完美的兼容 Vue.js 开发 Weex 界面。
Weex 又于 2017 年 2 月 24 迁移至 Apache 基金会,阿里巴巴会基于 Apache 的基础设施继续迭代。并启用了全新的 GitHub 仓库: https://github.com/apache/incubator-weex
故以下源码分析都基于 v0.10.0 这个版本。
上图是官方给的一张原理图,Weex 是如何把 JS 打包成 JS Bundle 的原理本篇文章暂时不涉及。本篇文章会详细分析 Weex 是如何在 Native 端工作的。笔者把 Native 端的原理再次细分,如下图:
Weex 可以通过自己设计的 DSL,书写. we 文件或者. vue 文件来开发界面,整个页面书写分成了 3 段,template、style、script,借鉴了成熟的 MVVM 的思想。
Weex 在性能方面,为了尽可能的提升客户端的性能,DSL 的 Transformer 全部都放在了服务器端实现,Weex 会在服务器端将 XML + CSS + JavaScript 代码全部都转换成 JS Bundle。服务器将 JS Bundle 部署到 Server 上和 CDN 上。
Weex 和 React Native 不同的是,Weex 把 JS Framework 内置在 SDK 里面,用来解析从服务器上下载的 JS Bundle,这样也减少了每个 JS Bundle 的体积,不再有 React Native 需要分包的问题。客户端请求完 JS Bundle 以后,传给 JS Framework,JS Framework 解析完成以后会输出 Json 格式的 Virtual DOM,客户端 Native 只需要专心负责 Virtual DOM 的解析和布局、UI 渲染。然而这一套解析,布局,渲染的逻辑 SDK 基本实现了。
最后 Weex 支持三端一致,服务器上的一份 JS Bundle,通过解析,实现 iOS/Android/HTML5 三端的一致性。
经过上一章的分析,我们知道了 Weex 的整体流程,由于笔者前端知识匮乏,所以从. we 或者. vue 文件到 JS bundle 前端这部分的源码分析本文暂时不涉及,等笔者熟悉前端以后,这块还会再补上来。
分析之前先说明一点,Weex 的所有源码其实已经开源了,至于 SDK 的 Demo 里面还依赖了一个 ATSDK.framework,这个是没有开源的。ATSDK.framework 这个其实是 Weex 性能监控的插件。
就是上图中的那个灰色的框框的插件。这个插件有些大厂有自己的 APM,阿里暂时没有开源这块,但是对 Weex 所有功能是不影响的。
那么接下来就详细分析一下在 iOS Native 端,Weex 是如何跑起来的。直接上源码分析。
(一). Weex SDK 初始化
这是 Native 端想把 Weex 跑起来的第一步。
- - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
- {
- self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
- self.window.backgroundColor = [UIColor whiteColor];
- // 在这里进行初始化SDK
- [self initWeexSDK];
- self.window.rootViewController = [[WXRootViewController alloc] initWithRootViewController:[self demoController]];
- [self.window makeKeyAndVisible];
- return YES;
- }
在 application: didFinishLaunchingWithOptions: 函数里面初始化 SDK。这里会初始化很多东西。可能有人会问了,初始化写在这里,还初始化这么多东西,不会卡 App 的启动时间么?带着这个问题继续往下看吧。
- #pragma mark weex
- - (void)initWeexSDK
- {
- [WXAppConfiguration setAppGroup:@"AliApp"];
- [WXAppConfiguration setAppName:@"WeexDemo"];
- [WXAppConfiguration setExternalUserAgent:@"ExternalUA"];
- [WXSDKEngine initSDKEnvironment];
- [WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)];
- [WXSDKEngine registerHandler:[WXEventModule new] withProtocol:@protocol(WXEventModuleProtocol)];
- [WXSDKEngine registerComponent:@"select" withClass:NSClassFromString(@"WXSelectComponent")];
- [WXSDKEngine registerModule:@"event" withClass:[WXEventModule class]];
- [WXSDKEngine registerModule:@"syncTest" withClass:[WXSyncTestModule class]];
- #if !(TARGET_IPHONE_SIMULATOR)
- [self checkUpdate];
- #endif
- #ifdef DEBUG
- [self atAddPlugin];
- [WXDebugTool setDebug:YES];
- [WXLog setLogLevel:WXLogLevelLog];
- #ifndef UITEST
- [[ATManager shareInstance] show];
- #endif
- #else
- [WXDebugTool setDebug:NO];
- [WXLog setLogLevel:WXLogLevelError];
- #endif
- }
上述就是要在 application: didFinishLaunchingWithOptions: 里面初始化的全部内容。我们一行一行的来解读。
WXAppConfiguration 是一个用来记录 App 配置信息的单例对象。
- @interface WXAppConfiguration : NSObject
- @property (nonatomic, strong) NSString * appGroup;
- @property (nonatomic, strong) NSString * appName;
- @property (nonatomic, strong) NSString * appVersion;
- @property (nonatomic, strong) NSString * externalUA;
- @property (nonatomic, strong) NSString * JSFrameworkVersion;
- @property (nonatomic, strong) NSArray * customizeProtocolClasses;
- /**
- * AppGroup的名字或者公司组织名,默认值为nil
- */
- + (NSString *)appGroup;
- + (void)setAppGroup:(NSString *) appGroup;
- /**
- * app的名字, 默认值是main bundle里面的CFBundleDisplayName
- */
- + (NSString *)appName;
- + (void)setAppName:(NSString *)appName;
- /**
- * app版本信息, 默认值是main bundle里面的CFBundleShortVersionString
- */
- + (NSString *)appVersion;
- + (void)setAppVersion:(NSString *)appVersion;
- /**
- * app外面用户代理的名字, 所有Weex的请求头都会设置用户代理user agent字段,默认值为nil
- */
- + (NSString *)externalUserAgent;
- + (void)setExternalUserAgent:(NSString *)userAgent;
- /**
- * JSFrameworkVersion的版本
- */
- + (NSString *)JSFrameworkVersion;
- + (void)setJSFrameworkVersion:(NSString *)JSFrameworkVersion;
- /*
- * 自定义customizeProtocolClasses
- */
- + (NSArray*)customizeProtocolClasses;
- + (void)setCustomizeProtocolClasses:(NSArray*)customizeProtocolClasses;
- @end
注意 WXAppConfiguration 的所有方法都是加号的类方法,内部实现是用 WXAppConfiguration 的单例实现的,这里用类方法是为了我们方便调用。
接下来是初始化 SDK 的实质代码了。
- [WXSDKEngine initSDKEnvironment];
关于初始化的具体实现,见下面,里面标注了注释:
- + (void)initSDKEnvironment
- {
- // 打点记录状态
- WX_MONITOR_PERF_START(WXPTInitalize)
- WX_MONITOR_PERF_START(WXPTInitalizeSync)
- // 加载本地的main.js
- NSString *filePath = [[NSBundle bundleForClass:self] pathForResource:@"main" ofType:@"js"];
- NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
- // 初始化SDK环境
- [WXSDKEngine initSDKEnvironment:script];
- // 打点记录状态
- WX_MONITOR_PERF_END(WXPTInitalizeSync)
- // 模拟器版本特殊代码
- #if TARGET_OS_SIMULATOR
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- [WXSimulatorShortcutManager registerSimulatorShortcutWithKey:@"i" modifierFlags:UIKeyModifierCommand | UIKeyModifierAlternate action:^{
- NSURL *URL = [NSURL URLWithString:@"http://localhost:8687/launchDebugger"];
- NSURLRequest *request = [NSURLRequest requestWithURL:URL];
- NSURLSession *session = [NSURLSession sharedSession];
- NSURLSessionDataTask *task = [session dataTaskWithRequest:request
- completionHandler:
- ^(NSData *data, NSURLResponse *response, NSError *error) {
- // ...
- }];
- [task resume];
- WXLogInfo(@"Launching browser...");
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- [self connectDebugServer:@"ws://localhost:8687/debugger/0/renderer"];
- });
- }];
- });
- #endif
- }
这里整个 SDKEnvironment 的初始化分成了四个步骤,WXMonitor 监视器记录状态,加载本地的 main.js,WXSDKEngine 的初始化,模拟器 WXSimulatorShortcutManager 连接本地 server。接下来一步步的分析。
1. WXMonitor 监视器记录状态
WXMonitor 是一个普通的对象,它里面只存储了一个线程安全的字典 WXThreadSafeMutableDictionary。
- @interface WXThreadSafeMutableDictionary<KeyType, ObjectType> : NSMutableDictionary
- @property (nonatomic, strong) dispatch_queue_t queue;
- @property (nonatomic, strong) NSMutableDictionary* dict;
- @end
在这个字典初始化的时候会初始化一个 queue。
- - (instancetype)init
- {
- self = [self initCommon];
- if (self) {
- _dict = [NSMutableDictionary dictionary];
- }
- return self;
- }
- - (instancetype)initCommon
- {
- self = [super init];
- if (self) {
- NSString* uuid = [NSString stringWithFormat:@"com.taobao.weex.dictionary_%p", self];
- _queue = dispatch_queue_create([uuid UTF8String], DISPATCH_QUEUE_CONCURRENT);
- }
- return self;
- }
每次生成一次 WXThreadSafeMutableDictionary,就会有一个与之内存地址向对应的 Concurrent 的 queue 相对应。
这个 queue 就保证了线程安全。
- - (NSUInteger)count
- {
- __block NSUInteger count;
- dispatch_sync(_queue, ^{
- count = _dict.count;
- });
- return count;
- }
- - (id)objectForKey:(id)aKey
- {
- __block id obj;
- dispatch_sync(_queue, ^{
- obj = _dict[aKey];
- });
- return obj;
- }
- - (NSEnumerator *)keyEnumerator
- {
- __block NSEnumerator *enu;
- dispatch_sync(_queue, ^{
- enu = [_dict keyEnumerator];
- });
- return enu;
- }
- - (id)copy{
- __block id copyInstance;
- dispatch_sync(_queue, ^{
- copyInstance = [_dict copy];
- });
- return copyInstance;
- }
count、objectForKey:、keyEnumerator、copy 这四个操作都是同步操作,用 dispatch_sync 保护线程安全。
- - (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey
- {
- aKey = [aKey copyWithZone:NULL];
- dispatch_barrier_async(_queue, ^{
- _dict[aKey] = anObject;
- });
- }
- - (void)removeObjectForKey:(id)aKey
- {
- dispatch_barrier_async(_queue, ^{
- [_dict removeObjectForKey:aKey];
- });
- }
- - (void)removeAllObjects{
- dispatch_barrier_async(_queue, ^{
- [_dict removeAllObjects];
- });
- }
setObject:forKey:、removeObjectForKey:、removeAllObjects 这三个操作加上了 dispatch_barrier_async。
WXMonitor 在整个 Weex 里面担任的职责是记录下各个操作的 tag 值和记录成功和失败的原因。WXMonitor 封装了各种宏来方便方法的调用。
- #define WX_MONITOR_PERF_START(tag) [WXMonitor performancePoint:tag willStartWithInstance:nil];
- #define WX_MONITOR_PERF_END(tag) [WXMonitor performancePoint:tag didEndWithInstance:nil];
- #define WX_MONITOR_INSTANCE_PERF_START(tag, instance) [WXMonitor performancePoint:tag willStartWithInstance:instance];
- #define WX_MONITOR_INSTANCE_PERF_END(tag, instance) [WXMonitor performancePoint:tag didEndWithInstance:instance];
- #define WX_MONITOR_PERF_SET(tag, value, instance) [WXMonitor performancePoint:tag didSetValue:value withInstance:instance];
- #define WX_MONITOR_INSTANCE_PERF_IS_RECORDED(tag, instance) [WXMonitor performancePoint:tag isRecordedWithInstance:instance]
- // 上面这些宏都会分别对应下面这些具体的方法实现。
- + (void)performancePoint:(WXPerformanceTag)tag willStartWithInstance:(WXSDKInstance *)instance;
- + (void)performancePoint:(WXPerformanceTag)tag didEndWithInstance:(WXSDKInstance *)instance;
- + (void)performancePoint:(WXPerformanceTag)tag didSetValue:(double)value withInstance:(WXSDKInstance *)instance;
- + (BOOL)performancePoint:(WXPerformanceTag)tag isRecordedWithInstance:(WXSDKInstance *)instance;
整个操作被定义成 2 类,一个是全局的操作,一个是具体的操作。
- typedef enum: NSUInteger {
- // global
- WXPTInitalize = 0,
- WXPTInitalizeSync,
- WXPTFrameworkExecute,
- // instance
- WXPTJSDownload,
- WXPTJSCreateInstance,
- WXPTFirstScreenRender,
- WXPTAllRender,
- WXPTBundleSize,
- WXPTEnd
- }
- WXPerformanceTag;
在 WXSDKInstance 初始化之前,所有的全局的 global 操作都会放在 WXMonitor 的 WXThreadSafeMutableDictionary 中。当 WXSDKInstance 初始化之后,即 WXPerformanceTag 中 instance 以下的所有操作都会放在 WXSDKInstance 的 performanceDict 中,注意 performanceDict 并不是线程安全的。
举个例子:
- + (void)performancePoint:(WXPerformanceTag)tag willStartWithInstance:(WXSDKInstance *)instance
- {
- NSMutableDictionary *performanceDict = [self performanceDictForInstance:instance];
- NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:2];
- dict[kStartKey] = @(CACurrentMediaTime() * 1000);
- performanceDict[@(tag)] = dict;
- }
所有的操作都会按照时间被记录下来:
- WX_MONITOR_PERF_START(WXPTInitalize)
- WX_MONITOR_PERF_START(WXPTInitalizeSync)
WXThreadSafeMutableDictionary 字典里面会存类似这些数据:
- {
- 0 = {
- start = "146297522.903652";
- };
- 1 = {
- start = "146578019.356428";
- };
- }
字典里面会根据操作的 tag 作为 key 值。一般 WX_MONITOR_PERF_START 和 WX_MONITOR_PERF_END 是成对出现的,初始化结束以后就会调用 WX_MONITOR_PERF_END。最终字典里面会保存成下面的样子:
- {
- 0 = {
- end = "148750673.312226";
- start = "148484241.723654";
- };
- 1 = {
- end = "148950673.312226";
- start = "148485865.699819";
- };
- }
WXMonitor 里面还会记录一些成功和失败的信息:
- #define WX_MONITOR_SUCCESS_ON_PAGE(tag, pageName) [WXMonitor monitoringPointDidSuccess:tag onPage:pageName];
- #define WX_MONITOR_FAIL_ON_PAGE(tag, errorCode, errorMessage, pageName) \
- NSError *error = [NSError errorWithDomain:WX_ERROR_DOMAIN \
- code:errorCode \
- userInfo:@{NSLocalizedDescriptionKey:(errorMessage?:@"No message")}]; \
- [WXMonitor monitoringPoint:tag didFailWithError:error onPage:pageName];
- #define WX_MONITOR_SUCCESS(tag) WX_MONITOR_SUCCESS_ON_PAGE(tag, nil)
- #define WX_MONITOR_FAIL(tag, errorCode, errorMessage) WX_MONITOR_FAIL_ON_PAGE(tag, errorCode, errorMessage, nil)
- // 上面这些宏都会分别对应下面这些具体的方法实现。
- + (void)monitoringPointDidSuccess:(WXMonitorTag)tag onPage:(NSString *)pageName;
- + (void)monitoringPoint:(WXMonitorTag)tag didFailWithError:(NSError *)error onPage:(NSString *)pageName;
这些函数暂时这里没有用到,暂时先不解析了。
2. 加载本地的 main.js
SDK 里面会带一个 main.js,直接打开这个文件会看到一堆经过 webpack 压缩之后的文件。
- import { subversion } from '../../../package.json'
- import runtime from '../../runtime'
- import frameworks from '../../frameworks/index'
- import services from '../../services/index'
- const { init, config } = runtime
- config.frameworks = frameworks
- const { native, transformer } = subversion
- for (const serviceName in services) {
- runtime.service.register(serviceName, services[serviceName])
- }
- runtime.freezePrototype()
- runtime.setNativeConsole()
- // register framework meta info
- global.frameworkVersion = native
- global.transformerVersion = transformer
- // init frameworks
- const globalMethods = init(config)
- // set global methods
- for (const methodName in globalMethods) {
- global[methodName] = (...args) => {
- const ret = globalMethods[methodName](...args)
- if (ret instanceof Error) {
- console.error(ret.toString())
- }
- return ret
- }
- }
这一段 js 是会被当做入参传递给 WXSDKManager。它也就是 Native 这边的 js framework。
3. WXSDKEngine 的初始化
WXSDKEngine 的初始化就是整个 SDK 初始化的关键。
- + (void)initSDKEnvironment:(NSString *)script
- {
- if (!script || script.length <= 0) {
- WX_MONITOR_FAIL(WXMTJSFramework, WX_ERR_JSFRAMEWORK_LOAD, @"framework loading is failure!");
- return;
- }
- // 注册Components,Modules,Handlers
- [self registerDefaults];
- // 执行JsFramework
- [[WXSDKManager bridgeMgr] executeJsFramework:script];
- }
总共干了两件事情,注册 Components,Modules,Handlers 和 执行 JSFramework。
先来看看是怎么注册的。
- + (void)registerDefaults
- {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- [self _registerDefaultComponents];
- [self _registerDefaultModules];
- [self _registerDefaultHandlers];
- });
- }
在 WXSDKEngine 初始化的时候就分别注册了这三样东西,Components,Modules,Handlers。
先看 Components:
- + (void)_registerDefaultComponents
- {
- [self registerComponent:@"container" withClass:NSClassFromString(@"WXDivComponent") withProperties:nil];
- [self registerComponent:@"div" withClass:NSClassFromString(@"WXComponent") withProperties:nil];
- [self registerComponent:@"text" withClass:NSClassFromString(@"WXTextComponent") withProperties:nil];
- [self registerComponent:@"image" withClass:NSClassFromString(@"WXImageComponent") withProperties:nil];
- [self registerComponent:@"scroller" withClass:NSClassFromString(@"WXScrollerComponent") withProperties:nil];
- [self registerComponent:@"list" withClass:NSClassFromString(@"WXListComponent") withProperties:nil];
- [self registerComponent:@"header" withClass:NSClassFromString(@"WXHeaderComponent")];
- [self registerComponent:@"cell" withClass:NSClassFromString(@"WXCellComponent")];
- [self registerComponent:@"embed" withClass:NSClassFromString(@"WXEmbedComponent")];
- [self registerComponent:@"a" withClass:NSClassFromString(@"WXAComponent")];
- [self registerComponent:@"select" withClass:NSClassFromString(@"WXSelectComponent")];
- [self registerComponent:@"switch" withClass:NSClassFromString(@"WXSwitchComponent")];
- [self registerComponent:@"input" withClass:NSClassFromString(@"WXTextInputComponent")];
- [self registerComponent:@"video" withClass:NSClassFromString(@"WXVideoComponent")];
- [self registerComponent:@"indicator" withClass:NSClassFromString(@"WXIndicatorComponent")];
- [self registerComponent:@"slider" withClass:NSClassFromString(@"WXSliderComponent")];
- [self registerComponent:@"web" withClass:NSClassFromString(@"WXWebComponent")];
- [self registerComponent:@"loading" withClass:NSClassFromString(@"WXLoadingComponent")];
- [self registerComponent:@"loading-indicator" withClass:NSClassFromString(@"WXLoadingIndicator")];
- [self registerComponent:@"refresh" withClass:NSClassFromString(@"WXRefreshComponent")];
- [self registerComponent:@"textarea" withClass:NSClassFromString(@"WXTextAreaComponent")];
- [self registerComponent:@"canvas" withClass:NSClassFromString(@"WXCanvasComponent")];
- [self registerComponent:@"slider-neighbor" withClass:NSClassFromString(@"WXSliderNeighborComponent")];
- }
在 WXSDKEngine 初始化的时候会默认注册这 23 种基础组件。这里就举一个最复杂的组件 WXWebComponent,来看看它是如何被注册的。
首先需要说明的一点,
- + (void)registerComponent:(NSString *)name withClass:(Class)clazz
- {
- [self registerComponent:name withClass:clazz withProperties: @{@"append":@"tree"}];
- }
registerComponent:withClass: 方法和 registerComponent:withClass:withProperties: 方法的区别在于最后一个入参是否传 @{@"append":@"tree"},如果被标记成了 @"tree",那么在 syncQueue 堆积了很多任务的时候,会被强制执行一次 layout。
所以上面 23 种基本组件里面,只有前 5 种,container,div,text,image,scroller,list 是没有被标记成 @"tree",剩下的 18 种都是有可能强制执行一次 layout。
- + (void)registerComponent:(NSString *)name withClass:(Class)clazz withProperties:(NSDictionary *)properties
- {
- if (!name || !clazz) {
- return;
- }
- WXAssert(name && clazz, @"Fail to register the component, please check if the parameters are correct !");
- // 1.WXComponentFactory注册组件的方法
- [WXComponentFactory registerComponent:name withClass:clazz withPros:properties];
- // 2.遍历出所有异步的方法
- NSMutableDictionary *dict = [WXComponentFactory componentMethodMapsWithName:name];
- dict[@"type"] = name;
- // 3.把组件注册到WXBridgeManager中
- if (properties) {
- NSMutableDictionary *props = [properties mutableCopy];
- if ([dict[@"methods"] count]) {
- [props addEntriesFromDictionary:dict];
- }
- [[WXSDKManager bridgeMgr] registerComponents:@[props]];
- } else {
- [[WXSDKManager bridgeMgr] registerComponents:@[dict]];
- }
- }
注册组件全部都是通过 WXComponentFactory 完成注册的。WXComponentFactory 是一个单例。
- @interface WXComponentFactory : NSObject
- {
- NSMutableDictionary *_componentConfigs;
- NSLock *_configLock;
- }
- @property (nonatomic, strong) NSDictionary *properties;
- @end
在 WXComponentFactory 中,_componentConfigs 会存储所有的组件配置,注册的过程也是生成_componentConfigs 的过程。
- - (void)registerComponent:(NSString *)name withClass:(Class)clazz withPros:(NSDictionary *)pros
- {
- WXAssert(name && clazz, @"name or clazz must not be nil for registering component.");
- WXComponentConfig *config = nil;
- [_configLock lock];
- config = [_componentConfigs objectForKey:name];
- // 如果组件已经注册过,会提示重复注册,并且覆盖原先的注册行为
- if(config){
- WXLogInfo(@"Overrider component name:%@ class:%@, to name:%@ class:%@",
- config.name, config.class, name, clazz);
- }
- config = [[WXComponentConfig alloc] initWithName:name class:NSStringFromClass(clazz) pros:pros];
- [_componentConfigs setValue:config forKey:name];
- // 注册类方法
- [config registerMethods];
- [_configLock unlock];
- }
在 WXComponentFactory 的_componentConfigs 字典中会按照组件的名字作为 key,WXComponentConfig 作为 value 存储各个组件的配置。
- @interface WXComponentConfig : WXInvocationConfig
- @property (nonatomic, strong) NSDictionary *properties;
- @end
- @interface WXInvocationConfig : NSObject
- @property (nonatomic, strong) NSString *name;
- @property (nonatomic, strong) NSString *clazz;
- @property (nonatomic, strong) NSMutableDictionary *asyncMethods;
- @property (nonatomic, strong) NSMutableDictionary *syncMethods;
- @end
WXComponentConfig 继承自 WXInvocationConfig,在 WXInvocationConfig 中存储了组件名 name,类名 clazz,类里面的同步方法字典 syncMethods 和异步方法字典 asyncMethods。
组件注册这里比较关键的一点是注册类方法。
- - (void)registerMethods
- {
- Class currentClass = NSClassFromString(_clazz);
- if (!currentClass) {
- WXLogWarning(@"The module class [%@] doesn't exit!", _clazz);
- return;
- }
- while (currentClass != [NSObject class]) {
- unsigned int methodCount = 0;
- // 获取类的方法列表
- Method *methodList = class_copyMethodList(object_getClass(currentClass), &methodCount);
- for (unsigned int i = 0; i < methodCount; i++) {
- // 获取SEL的字符串名称
- NSString *selStr = [NSString stringWithCString:sel_getName(method_getName(methodList[i])) encoding:NSUTF8StringEncoding];
- BOOL isSyncMethod = NO;
- // 如果是SEL名字带sync,就是同步方法
- if ([selStr hasPrefix:@"wx_export_method_sync_"]) {
- isSyncMethod = YES;
- // 如果是SEL名字不带sync,就是异步方法
- } else if ([selStr hasPrefix:@"wx_export_method_"]) {
- isSyncMethod = NO;
- } else {
- // 如果名字里面不带wx_export_method_前缀的方法,那么都不算是暴露出来的方法,直接continue,进行下一轮的筛选
- continue;
- }
- NSString *name = nil, *method = nil;
- SEL selector = NSSelectorFromString(selStr);
- if ([currentClass respondsToSelector:selector]) {
- method = ((NSString* (*)(id, SEL))[currentClass methodForSelector:selector])(currentClass, selector);
- }
- if (method.length <= 0) {
- WXLogWarning(@"The module class [%@] doesn't has any method!", _clazz);
- continue;
- }
- // 去掉方法名里面带的:号
- NSRange range = [method rangeOfString:@":"];
- if (range.location != NSNotFound) {
- name = [method substringToIndex:range.location];
- } else {
- name = method;
- }
- // 最终字典里面会按照异步方法和同步方法保存到最终的方法字典里
- NSMutableDictionary *methods = isSyncMethod ? _syncMethods : _asyncMethods;
- [methods setObject:method forKey:name];
- }
- free(methodList);
- currentClass = class_getSuperclass(currentClass);
- }
- }
这里的做法也比较常规,找到对应的类方法,判断名字里面是否带有 "sync" 来判断方法是同步还是异步方法。这里重点需要解析的是组件的方法是如何转换成类方法的暴露出去的。
Weex 是通过里面通过 WX_EXPORT_METHOD 宏做到对外暴露类方法的。
- #define WX_EXPORT_METHOD(method) WX_EXPORT_METHOD_INTERNAL(method,wx_export_method_)
- #define WX_EXPORT_METHOD_INTERNAL(method, token) \
- + (NSString *)WX_CONCAT_WRAPPER(token, __LINE__) { \
- return NSStringFromSelector(method); \
- }
- #define WX_CONCAT_WRAPPER(a, b) WX_CONCAT(a, b)
- #define WX_CONCAT(a, b) a ## b
WX_EXPORT_METHOD 宏会完全展开成下面这个样子:
- #define WX_EXPORT_METHOD(method)
- + (NSString *)wx_export_method_ __LINE__ { \
- return NSStringFromSelector(method); \
- }
举个例子,在 WXWebComponent 的第 52 行里面写了下面这一行代码:
- WX_EXPORT_METHOD(@selector(goBack))
那么这个宏在预编译的时候就会被展开成下面这个样子:
- + (NSString *)wx_export_method_52 {
- return NSStringFromSelector(@selector(goBack));
- }
于是乎在 WXWebComponent 的类方法里面就多了一个 wx_export_method_52 的方法。由于在同一个文件里面,WX_EXPORT_METHOD 宏是不允许写在同一行的,所以转换出来的方法名字肯定不会相同。但是不同类里面行数就没有规定,行数是可能相同的,从而不同类里面可能就有相同的方法名。
比如在 WXScrollerComponent 里面的第 58 行
- WX_EXPORT_METHOD(@selector(resetLoadmore))
WXTextAreaComponent 里面的第 58 行
- WX_EXPORT_METHOD(@selector(focus))
这两个是不同的组件,但是宏展开之后的方法名是一样的,这两个不同的类的类方法,是有重名的,但是完全不会有什么影响,因为获取类方法的时候是通过 class_copyMethodList,保证这个 list 里面都是唯一的名字即可。
还有一点需要说明的是,虽然用 class_copyMethodList 会获取所有的类方法 (+ 号方法),但是可能有人疑问了,那不通过 WX_EXPORT_METHOD 宏对外暴露的普通的 + 号方法,不是也会被筛选进来么?
回答:是的,会被 class_copyMethodList 获取到,但是这里有一个判断条件,会避开这些不通过 WX_EXPORT_METHOD 宏对外暴露的普通的 + 号类方法。
如果不通过 WX_EXPORT_METHOD 宏来申明对外暴露的普通的 + 号类方法,那么名字里面就不会带 wx_export_method_的前缀的方法,那么都不算是暴露出来的方法,上面筛选的代码里面会直接 continue,进行下一轮的筛选,所以不必担心那些普通的 + 号类方法会进来干扰。
回到 WXWebComponent 注册,通过上述方法获取完类方法之后,字典里面就存储的如下信息:
- methods = {
- goBack = goBack;
- goForward = goForward;
- reload = reload;
- }
这就完成了组件注册的第一步,完成了注册配置 WXComponentConfig。
组件注册的第二步,遍历所有的异步方法。
- - (NSMutableDictionary *)_componentMethodMapsWithName:(NSString *)name
- {
- NSMutableDictionary *dict = [NSMutableDictionary dictionary];
- NSMutableArray *methods = [NSMutableArray array];
- [_configLock lock];
- [dict setValue:methods forKey:@"methods"];
- WXComponentConfig *config = _componentConfigs[name];
- void (^mBlock)(id, id, BOOL *) = ^(id mKey, id mObj, BOOL * mStop) {
- [methods addObject:mKey];
- };
- [config.asyncMethods enumerateKeysAndObjectsUsingBlock:mBlock];
- [_configLock unlock];
- return dict;
- }
这里依旧是调用了 WXComponentFactory 的方法_componentMethodMapsWithName:。这里就是遍历出异步方法,并放入字典中,返回异步方法的字典。
还是以最复杂的 WXWebComponent 为例,这里就会返回如下的异步方法字典:
- {
- methods = (
- goForward,
- goBack,
- reload
- );
- }
注册组件的最后一步会在 JSFrame 中注册组件。
- @interface WXSDKManager ()
- @property (nonatomic, strong) WXBridgeManager *bridgeMgr;
- @property (nonatomic, strong) WXThreadSafeMutableDictionary *instanceDict;
- @end
在 WXSDKManager 里面会强持有一个 WXBridgeManager。这个 WXBridgeManager 就是用来和 JS 交互的 Bridge。
- @interface WXBridgeManager : NSObject
- @property (nonatomic, weak, readonly) WXSDKInstance *topInstance;
- @property (nonatomic, strong) WXBridgeContext *bridgeCtx;
- @property (nonatomic, assign) BOOL stopRunning;
- @property (nonatomic, strong) NSMutableArray *instanceIdStack;
- @end
WXBridgeManager 中会弱引用 WXSDKInstance 实例,是为了能调用 WXSDKInstance 的一些属性和方法。WXBridgeManager 里面最重要的一个属性就是 WXBridgeContext。
- @interface WXBridgeContext ()
- @property (nonatomic, weak, readonly) WXSDKInstance *topInstance;
- @property (nonatomic, strong) id<WXBridgeProtocol> jsBridge;
- @property (nonatomic, strong) WXDebugLoggerBridge *devToolSocketBridge;
- @property (nonatomic, assign) BOOL debugJS;
- // 存储native要即将调用js的一些方法
- @property (nonatomic, strong) NSMutableDictionary *sendQueue;
- // 实例的一些堆栈
- @property (nonatomic, strong) WXThreadSafeMutableArray *insStack;
- // 标识JSFramework是否已经加载完成
- @property (nonatomic) BOOL frameworkLoadFinished;
- // 在JSFramework加载完成之前,临时存储一些方法
- @property (nonatomic, strong) NSMutableArray *methodQueue;
- // 存储js模板的service
- @property (nonatomic, strong) NSMutableArray *jsServiceQueue;
- @end
在 WXBridgeContext 中强持有了一个 jsBridge。这个就是用来和 js 进行交互的 Bridge。
三者的关系用图表示出来如上图。由于是弱引用,所以用虚的线框表示。
回到注册的步骤中来,在 WXSDKEngine 中调用如下方法:
- [[WXSDKManager bridgeMgr] registerComponents:@[dict]];
WXBridgeManager 调用 registerComponents 方法。
- - (void)registerComponents:(NSArray *)components
- {
- if (!components) return;
- __weak typeof(self) weakSelf = self;
- WXPerformBlockOnBridgeThread(^(){
- [weakSelf.bridgeCtx registerComponents:components];
- });
- }
最终是 WXBridgeManager 里面的 WXBridgeContext 调用 registerComponents,进行组件的注册。但是注册组件的这一步是在一个特殊的线程中执行的。
- void WXPerformBlockOnBridgeThread(void (^block)())
- {
- [WXBridgeManager _performBlockOnBridgeThread:block];
- }
- + (void)_performBlockOnBridgeThread:(void (^)())block
- {
- if ([NSThread currentThread] == [self jsThread]) {
- block();
- } else {
- [self performSelector:@selector(_performBlockOnBridgeThread:)
- onThread:[self jsThread]
- withObject:[block copy]
- waitUntilDone:NO];
- }
- }
这里就可以看到,block 闭包是在 jsThread 的线程中执行的,并非主线程。WXBridgeManager 会新建一个名为 @"com.taobao.weex.bridge" 的 jsThread 线程,所有的组件注册都在这个子线程中执行的。这个 jsThread 也是一个单例,全局唯一。
- + (NSThread *)jsThread
- {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- WXBridgeThread = [[NSThread alloc] initWithTarget:[[self class]sharedManager] selector:@selector(_runLoopThread) object:nil];
- [WXBridgeThread setName:WX_BRIDGE_THREAD_NAME];
- if(WX_SYS_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
- [WXBridgeThread setQualityOfService:[[NSThread mainThread] qualityOfService]];
- } else {
- [WXBridgeThread setThreadPriority:[[NSThread mainThread] threadPriority]];
- }
- [WXBridgeThread start];
- });
- return WXBridgeThread;
- }
这里就是创建 jsThread 的代码,jsThread 会把 @selector(_runLoopThread) 作为 selector。
- - (void)_runLoopThread
- {
- [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
- while (!_stopRunning) {
- @autoreleasepool {
- [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
- }
- }
- }
于是这里就给 jsThread 开启了一个 runloop。这里是用 [NSMachPort port] 的方式开启的 runloop,之后再也无法获取到这个 port 了,而且这个 runloop 不是 CFRunloop,所以用官方文档上的那 3 个方法已经不能停止这个 runloop 了,只能自己通过 while 的方式来停止。上述代码是一种写法,当然 StackOverFlow 上面推荐的是下面的写法,下面的写法也是我常用的写法。
- BOOL shouldKeepRunning = YES; // global
- NSRunLoop *theRL = [NSRunLoop currentRunLoop];
- while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
- - (void)registerComponents:(NSArray *)components
- {
- WXAssertBridgeThread();
- if(!components) return;
- [self callJSMethod:@"registerComponents" args:@[components]];
- }
在 WXBridgeContext 中注册组件,其实调用的是 js 的方法 "registerComponents"。
这里有一个需要注意的一点,由于是在子线程上注册组件,那么 JSFramework 如果没有加载完成,native 去调用 js 的方法,必定调用失败。所以需要在 JSFramework 加载完成之前,把 native 调用 JS 的方法都缓存起来,一旦 JSFramework 加载完成,把缓存里面的方法都丢给 JSFramework 去加载。
- - (void)callJSMethod:(NSString *)method args:(NSArray *)args
- {
- if (self.frameworkLoadFinished) {
- [self.jsBridge callJSMethod:method args:args];
- } else {
- [_methodQueue addObject:@{@"method":method, @"args":args}];
- }
- }
所以在 WXBridgeContext 中需要一个 NSMutableArray,用来缓存在 JSFramework 加载完成之前,调用 JS 的方法。这里是保存在_methodQueue 里面。如果 JSFramework 加载完成,那么就会调用 callJSMethod:args: 方法。
- - (JSValue *)callJSMethod:(NSString *)method args:(NSArray *)args
- {
- WXLogDebug(@"Calling JS... method:%@, args:%@", method, args);
- return [[_jsContext globalObject] invokeMethod:method withArguments:args];
- }
由于这些注册的方法的定义是全局函数,那么很显然应该在 JSContext 的 globalObject 对象上调用该方法。(目前流程进行到这里还看不到定义的全局函数,往后看就会看到)
还是用 WXWebComponent 来举例,那么这里注册组件的 method 就是 @"registerComponents",args 参数如下:
- (
- {
- append = tree;
- methods = (
- goForward,
- goBack,
- reload
- );
- type = web;
- }
- )
实际上程序运行到这里,并不会去执行 callJSMethod:args:,因为现在 JSFramework 还没有加载完成。
注册组件的全部流程如下:
再注册 Modules
注册 Modules 的流程和上面注册 Components 非常类似。
- + (void)_registerDefaultModules
- {
- [self registerModule:@"dom" withClass:NSClassFromString(@"WXDomModule")];
- [self registerModule:@"navigator" withClass:NSClassFromString(@"WXNavigatorModule")];
- [self registerModule:@"stream" withClass:NSClassFromString(@"WXStreamModule")];
- [self registerModule:@"animation" withClass:NSClassFromString(@"WXAnimationModule")];
- [self registerModule:@"modal" withClass:NSClassFromString(@"WXModalUIModule")];
- [self registerModule:@"webview" withClass:NSClassFromString(@"WXWebViewModule")];
- [self registerModule:@"instanceWrap" withClass:NSClassFromString(@"WXInstanceWrap")];
- [self registerModule:@"timer" withClass:NSClassFromString(@"WXTimerModule")];
- [self registerModule:@"storage" withClass:NSClassFromString(@"WXStorageModule")];
- [self registerModule:@"clipboard" withClass:NSClassFromString(@"WXClipboardModule")];
- [self registerModule:@"globalEvent" withClass:NSClassFromString(@"WXGlobalEventModule")];
- [self registerModule:@"canvas" withClass:NSClassFromString(@"WXCanvasModule")];
- [self registerModule:@"picker" withClass:NSClassFromString(@"WXPickerModule")];
- [self registerModule:@"meta" withClass:NSClassFromString(@"WXMetaModule")];
- [self registerModule:@"webSocket" withClass:NSClassFromString(@"WXWebSocketModule")];
- }
WXSDKEngine 会默认注册这 15 种基础模块。这里就以比较复杂的模块 WXWebSocketModule 为例,来看看它是如何被注册的。
- + (void)registerModule:(NSString *)name withClass:(Class)clazz
- {
- WXAssert(name && clazz, @"Fail to register the module, please check if the parameters are correct !");
- // 1. WXModuleFactory注册模块
- NSString *moduleName = [WXModuleFactory registerModule:name withClass:clazz];
- // 2.遍历所有同步和异步方法
- NSDictionary *dict = [WXModuleFactory moduleMethodMapsWithName:moduleName];
- // 3.把模块注册到WXBridgeManager中
- [[WXSDKManager bridgeMgr] registerModules:dict];
- }
注册模块也分 3 步,第一步是在 WXModuleFactory 中注册。
- @interface WXModuleFactory ()
- @property (nonatomic, strong) NSMutableDictionary *moduleMap;
- @property (nonatomic, strong) NSLock *moduleLock;
- @end
在 WXModuleFactory 中,moduleMap 会存储所有的模块的配置信息,注册的过程也是生成 moduleMap 的过程。
- - (NSString *)_registerModule:(NSString *)name withClass:(Class)clazz
- {
- WXAssert(name && clazz, @"Fail to register the module, please check if the parameters are correct !");
- [_moduleLock lock];
- // 这里需要注意的是:注册模块是允许同名模块的
- WXModuleConfig *config = [[WXModuleConfig alloc] init];
- config.name = name;
- config.clazz = NSStringFromClass(clazz);
- [config registerMethods];
- [_moduleMap setValue:config forKey:name];
- [_moduleLock unlock];
- return name;
- }
整个注册的过程就是把 WXModuleConfig 为 value,name 为 key,存入_moduleMap 字典里。
- @interface WXModuleConfig : WXInvocationConfig
- @end
WXModuleConfig 仅仅只是继承自 WXInvocationConfig,所以它和 WXInvocationConfig 是完全一样的。[config registerMethods] 这个方法和注册组件的方法是同一个方法,具体注册流程这里就不再赘述了。
在 WXModuleFactory 中会记录下一个个的 WXModuleConfig:
- _moduleMap = {
- animation = "<WXModuleConfig: 0x60000024a230>";
- canvas = "<WXModuleConfig: 0x608000259ce0>";
- clipboard = "<WXModuleConfig: 0x608000259b30>";
- dom = "<WXModuleConfig: 0x608000259440>";
- event = "<WXModuleConfig: 0x60800025a280>";
- globalEvent = "<WXModuleConfig: 0x60000024a560>";
- instanceWrap = "<WXModuleConfig: 0x608000259a70>";
- meta = "<WXModuleConfig: 0x60000024a7a0>";
- modal = "<WXModuleConfig: 0x6080002597d0>";
- navigator = "<WXModuleConfig: 0x600000249fc0>";
- picker = "<WXModuleConfig: 0x608000259e60>";
- storage = "<WXModuleConfig: 0x60000024a4a0>";
- stream = "<WXModuleConfig: 0x6080002596e0>";
- syncTest = "<WXModuleConfig: 0x60800025a520>";
- timer = "<WXModuleConfig: 0x60000024a380>";
- webSocket = "<WXModuleConfig: 0x608000259fb0>";
- webview = "<WXModuleConfig: 0x6080002598f0>";
- }
每个 WXModuleConfig 中会记录下所有的同步和异步的方法。
- config.name = dom,
- config.clazz = WXDomModule,
- config.asyncMethods = {
- addElement = "addElement:element:atIndex:";
- addEvent = "addEvent:event:";
- addRule = "addRule:rule:";
- createBody = "createBody:";
- createFinish = createFinish;
- getComponentRect = "getComponentRect:callback:";
- moveElement = "moveElement:parentRef:index:";
- refreshFinish = refreshFinish;
- removeElement = "removeElement:";
- removeEvent = "removeEvent:event:";
- scrollToElement = "scrollToElement:options:";
- updateAttrs = "updateAttrs:attrs:";
- updateFinish = updateFinish;
- updateStyle = "updateStyle:styles:";
- },
- config.syncMethods = {
- }
第二步遍历所有的方法列表。
- - (NSMutableDictionary *)_moduleMethodMapsWithName:(NSString *)name
- {
- NSMutableDictionary *dict = [NSMutableDictionary dictionary];
- NSMutableArray *methods = [self _defaultModuleMethod];
- [_moduleLock lock];
- [dict setValue:methods forKey:name];
- WXModuleConfig *config = _moduleMap[name];
- void (^mBlock)(id, id, BOOL *) = ^(id mKey, id mObj, BOOL * mStop) {
- [methods addObject:mKey];
- };
- [config.syncMethods enumerateKeysAndObjectsUsingBlock:mBlock];
- [config.asyncMethods enumerateKeysAndObjectsUsingBlock:mBlock];
- [_moduleLock unlock];
- return dict;
- }
这里遍历模块的方法列表和组件的有所不同。首先模块是有默认方法的。
- - (NSMutableArray*)_defaultModuleMethod
- {
- return [NSMutableArray arrayWithObjects:@"addEventListener",@"removeAllEventListeners", nil];
- }
所有的模块都有 addEventListener 和 removeAllEventListeners 方法。第二个不同就是模块会遍历所有的同步和异步方法,(组件只会遍历异步方法)。最终返回生成模块的所有方法的字典。
以 dom 模块为例,它返回的字典如下:
- {
- dom = (
- addEventListener,
- removeAllEventListeners,
- addEvent,
- removeElement,
- updateFinish,
- getComponentRect,
- scrollToElement,
- addRule,
- updateAttrs,
- addElement,
- createFinish,
- createBody,
- updateStyle,
- removeEvent,
- refreshFinish,
- moveElement
- );
- }
最后一步也是在 WXBridgeManager 注册模块。
- - (void)registerModules:(NSDictionary *)modules
- {
- if (!modules) return;
- __weak typeof(self) weakSelf = self;
- WXPerformBlockOnBridgeThread(^(){
- [weakSelf.bridgeCtx registerModules:modules];
- });
- }
这里注册过程和组件是完全一样的,也是在子线程 @"com.taobao.weex.bridge" 的 jsThread 中操作的。
- - (void)registerModules:(NSDictionary *)modules
- {
- WXAssertBridgeThread();
- if(!modules) return;
- [self callJSMethod:@"registerModules" args:@[modules]];
- }
这里调用 JS 的方法名变为了 @"registerModules",入参 args 就是第二步产生的方法字典。
- args = (
- {
- dom = (
- addEventListener,
- removeAllEventListeners,
- addEvent,
- removeElement,
- updateFinish,
- getComponentRect,
- scrollToElement,
- addRule,
- updateAttrs,
- addElement,
- createFinish,
- createBody,
- updateStyle,
- removeEvent,
- refreshFinish,
- moveElement
- );
- }
- )
同样,此时模块并不会真正的被注册上,因为 JSFramework 还没有加载完成,这里也会被添加进 methodQueue 缓存起来。
注册模块的全部流程如下:
最后是注册 Handlers。
- + (void)_registerDefaultHandlers
- {
- [self registerHandler:[WXResourceRequestHandlerDefaultImpl new] withProtocol:@protocol(WXResourceRequestHandler)];
- [self registerHandler:[WXNavigationDefaultImpl new] withProtocol:@protocol(WXNavigationProtocol)];
- [self registerHandler:[WXURLRewriteDefaultImpl new] withProtocol:@protocol(WXURLRewriteProtocol)];
- [self registerHandler:[WXWebSocketDefaultImpl new] withProtocol:@protocol(WXWebSocketHandler)];
- }
WXSDKEngine 中默认注册 4 个 Handler。
- + (void)registerHandler:(id)handler withProtocol:(Protocol *)protocol
- {
- WXAssert(handler && protocol, @"Fail to register the handler, please check if the parameters are correct !");
- [WXHandlerFactory registerHandler:handler withProtocol:protocol];
- }
WXSDKEngine 会继续调用 WXHandlerFactory 的 registerHandler:withProtocol: 方法。
- @interface WXHandlerFactory : NSObject
- @property (nonatomic, strong) WXThreadSafeMutableDictionary *handlers;
- + (void)registerHandler:(id)handler withProtocol:(Protocol *)protocol;
- + (id)handlerForProtocol:(Protocol *)protocol;
- + (NSDictionary *)handlerConfigs;
- @end
WXHandlerFactory 也是一个单例,里面有一个线程安全的字典 handlers,用来保存实例和 Protocol 名的映射表。
WXSDKEngine 初始化的最后一步就是执行 JSFramework。
- [[WXSDKManager bridgeMgr] executeJsFramework:script];
WXSDKManager 会调用 WXBridgeManager 去执行 SDK 里面的 main.js 文件。
- - (void)executeJsFramework:(NSString *)script
- {
- if (!script) return;
- __weak typeof(self) weakSelf = self;
- WXPerformBlockOnBridgeThread(^(){
- [weakSelf.bridgeCtx executeJsFramework:script];
- });
- }
WXBridgeManager 通过 WXBridgeContext 调用 executeJsFramework: 方法。这里方法调用也是在子线程中进行的。
- - (void)executeJsFramework:(NSString *)script
- {
- WXAssertBridgeThread();
- WXAssertParam(script);
- WX_MONITOR_PERF_START(WXPTFrameworkExecute);
- [self.jsBridge executeJSFramework:script];
- WX_MONITOR_PERF_END(WXPTFrameworkExecute);
- if ([self.jsBridge exception]) {
- NSString *message = [NSString stringWithFormat:@"JSFramework executes error: %@", [self.jsBridge exception]];
- WX_MONITOR_FAIL(WXMTJSFramework, WX_ERR_JSFRAMEWORK_EXECUTE, message);
- } else {
- WX_MONITOR_SUCCESS(WXMTJSFramework);
- // 至此JSFramework算完全加载完成了
- self.frameworkLoadFinished = YES;
- // 执行所有注册的JsService
- [self executeAllJsService];
- // 获取JSFramework版本号
- JSValue *frameworkVersion = [self.jsBridge callJSMethod:@"getJSFMVersion" args:nil];
- if (frameworkVersion && [frameworkVersion isString]) {
- // 把版本号存入WXAppConfiguration中
- [WXAppConfiguration setJSFrameworkVersion:[frameworkVersion toString]];
- }
- // 执行之前缓存在_methodQueue数组里面的所有方法
- for (NSDictionary *method in _methodQueue) {
- [self callJSMethod:method[@"method"] args:method[@"args"]];
- }
- [_methodQueue removeAllObjects];
- // 至此,初始化工作算完成了。
- WX_MONITOR_PERF_END(WXPTInitalize);
- };
- }
WX_MONITOR_PERF_START 是在操作之前标记 WXPTFrameworkExecute。执行完 JSFramework 以后,用 WX_MONITOR_PERF_END 标记执行完成。
- - (void)executeJSFramework:(NSString *)frameworkScript
- {
- WXAssertParam(frameworkScript);
- if (WX_SYS_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
- [_jsContext evaluateScript:frameworkScript withSourceURL:[NSURL URLWithString:@"main.js"]];
- }else{
- [_jsContext evaluateScript:frameworkScript];
- }
- }
加载 JSFramework 的核心代码在这里,通过 JSContext 执行 evaluateScript: 来加载 JSFramework。由于这里并没有返回值,所以加载的 JSFramework 的目的仅仅是声明了里面的所有方法,并没有调用。这也符合 OC 加载其他 Framework 的过程,加载只是加载到内存中,Framework 里面的方法可以随时被调用,而不是一加载就调用其所有的方法。
加载完成 JSFramework 以后,就要开始加载之前缓存的 JSService 和 JSMethod。JSService 是在 jsServiceQueue 中缓存的。JSMethod 是在 methodQueue 中缓存的。
- - (void)executeAllJsService
- {
- for(NSDictionary *service in _jsServiceQueue) {
- NSString *script = [service valueForKey:@"script"];
- NSString *name = [service valueForKey:@"name"];
- [self executeJsService:script withName:name];
- }
- [_jsServiceQueue removeAllObjects];
- }
JSService 由于是直接 js 转成 NSString,所以这里直接运行 executeJsService:withName 即可。
- for (NSDictionary *method in _methodQueue) {
- [self callJSMethod:method[@"method"] args:method[@"args"]];
- }
- [_methodQueue removeAllObjects];
- - (JSValue *)callJSMethod:(NSString *)method args:(NSArray *)args
- {
- WXLogDebug(@"Calling JS... method:%@, args:%@", method, args);
- NSLog(@"WXJSCoreBridge jsContext 正要调用方法");
- return [[_jsContext globalObject] invokeMethod:method withArguments:args];
- }
由于_methodQueue 里面装的都是全局的 js 方法,所以需要调用 invokeMethod: withArguments: 去执行。
当这一切都加载完成,SDK 的初始化工作就基本完成了,这里就会标记上 WXPTInitalize 结束。
这里还需要说明的是,jsBridge 第一次是如何被加载进来的。
- - (id<WXBridgeProtocol>)jsBridge
- {
- WXAssertBridgeThread();
- _debugJS = [WXDebugTool isDevToolDebug];
- Class bridgeClass = _debugJS ? NSClassFromString(@"WXDebugger") : [WXJSCoreBridge class];
- if (_jsBridge && [_jsBridge isKindOfClass:bridgeClass]) {
- return _jsBridge;
- }
- if (_jsBridge) {
- [_methodQueue removeAllObjects];
- _frameworkLoadFinished = NO;
- }
- _jsBridge = _debugJS ? [NSClassFromString(@"WXDebugger") alloc] : [[WXJSCoreBridge alloc] init];
- [self registerGlobalFunctions];
- return _jsBridge;
- }
第一次进入这个函数没有 jsBridge 实例的时候,会先生成 WXJSCoreBridge 的实例,然后紧接着注册全局的函数。等第二次再调用这个函数的时候,_jsBridge 已经是 WXJSCoreBridge 类型了,就会直接 return,下面的语句也不会再重复执行了。
- typedef NSInteger(^WXJSCallNative)(NSString *instance, NSArray *tasks, NSString *callback);
- typedef NSInteger(^WXJSCallAddElement)(NSString *instanceId, NSString *parentRef, NSDictionary *elementData, NSInteger index);
- typedef NSInvocation *(^WXJSCallNativeModule)(NSString *instanceId, NSString *moduleName, NSString *methodName, NSArray *args, NSDictionary *options);
- typedef void (^WXJSCallNativeComponent)(NSString *instanceId, NSString *componentRef, NSString *methodName, NSArray *args, NSDictionary *options);
这 4 个闭包就是 OC 封装暴露给 JS 的 4 个全局函数。
- - (void)registerCallNative:(WXJSCallNative)callNative
- {
- JSValue* (^callNativeBlock)(JSValue *, JSValue *, JSValue *) = ^JSValue*(JSValue *instance, JSValue *tasks, JSValue *callback){
- NSString *instanceId = [instance toString];
- NSArray *tasksArray = [tasks toArray];
- NSString *callbackId = [callback toString];
- WXLogDebug(@"Calling native... instance:%@, tasks:%@, callback:%@", instanceId, tasksArray, callbackId);
- return [JSValue valueWithInt32:(int32_t)callNative(instanceId, tasksArray, callbackId) inContext:[JSContext currentContext]];
- };
- _jsContext[@"callNative"] = callNativeBlock;
- }
- - (void)registerCallAddElement:(WXJSCallAddElement)callAddElement
- {
- id callAddElementBlock = ^(JSValue *instanceId, JSValue *ref, JSValue *element, JSValue *index, JSValue *ifCallback) {
- NSString *instanceIdString = [instanceId toString];
- NSDictionary *componentData = [element toDictionary];
- NSString *parentRef = [ref toString];
- NSInteger insertIndex = [[index toNumber] integerValue];
- WXLogDebug(@"callAddElement...%@, %@, %@, %ld", instanceIdString, parentRef, componentData, (long)insertIndex);
- return [JSValue valueWithInt32:(int32_t)callAddElement(instanceIdString, parentRef, componentData, insertIndex) inContext:[JSContext currentContext]];
- };
- _jsContext[@"callAddElement"] = callAddElementBlock;
- }
- - (void)registerCallNativeModule:(WXJSCallNativeModule)callNativeModuleBlock
- {
- _jsContext[@"callNativeModule"] = ^JSValue *(JSValue *instanceId, JSValue *moduleName, JSValue *methodName, JSValue *args, JSValue *options) {
- NSString *instanceIdString = [instanceId toString];
- NSString *moduleNameString = [moduleName toString];
- NSString *methodNameString = [methodName toString];
- NSArray *argsArray = [args toArray];
- NSDictionary *optionsDic = [options toDictionary];
- WXLogDebug(@"callNativeModule...%@,%@,%@,%@", instan
来源: http://www.open-open.com/lib/view/open1490065108771.html