iOS-Weex探索之iOS端WeexSDK源码解读

高佳杰

前言:由于公司采用weex来开发项目,在开发过程中遇到了很多坑,在这种背景下花了几天时间来研究一下weexSDK的源码如果有理解不当的地方欢迎指正。

1.iOS weexSDK 概述

Weex 是一套简单易用的跨平台开发方案,能以 web 的开发体验构建高性能、可扩展的 native 应用,为了做到这些,Weex 与 Vue 合作,使用 Vue 作为上层框架,并遵循 W3C 标准实现了统一的 JSEngine 和 DOM API,这样一来,你甚至可以使用其他框架驱动 Weex,打造三端一致的 native 应用。
alt alt

alt

2.weex工作原理

alt 以上是weex官方提供的原理图

Weex把JS Framework内置在客户端的SDK里面,用来解析从服务器上下载的或本地加载的JS Bundle,客户端拿到JS Bundle以后,传给JS Framework,JS Framework解析完成以后会输出Json格式的Virtual DOM,客户端Native只需要专心负责 Virtual DOM 的解析和布局、UI 渲染。然而这一套解析,布局,渲染的逻辑SDK基本实现。最后weex通过3端sdk解析JS Bundle 实现iOS/Android/HTML5 三端的一致性

3.weexSDK 基本使用

1.使用WXAppConfiguration 配置app基本信息,初始化weex引擎[WXSDKEngine initSDKEnvironment]
2.使用WXSDKInstance加载url

4. weexSDK 源码分析

  • 4.1 weex引擎初始化的过程
  • 4.2 weex引擎加载weex页面的的过程
  • 4.3 weex页面与native交互的过程
  • 4.4 总结
4.1.weex引擎初始化的过程
4.1.1简要概括
  1. 使用WXAppConfiguration加载app的基本配置包括AppName,AppGroup等等
  2. 通过JSContext加载jsframework main.js文件进内存备用
  3. 在jsframework 加载完毕的前提下通过JSContext调用的jsframework的registerComponents方法加载默认组件
  4. 在jsframework 加载完毕的前提下通过JSContext调用的jsframework的registerModules方法加载默认模块
  5. 将默认的handlers加载给WXHandlerFactory的handlers存储起来备用
4.1.2详细过程分析
  • 1.使用WXAppConfiguration加载app的基本配置包括AppName,AppGroup等等
+ (void)weexSdk
{
    // app信息配置
    [WXAppConfiguration setAppGroup:@"dianwoda"];
    [WXAppConfiguration setAppName:@"点我达"];
    [WXAppConfiguration setExternalUserAgent:@"ExternalUA"];
......
}
  • 2.通过JSContext加载jsframework main.js文件进内存备用/br>
+ (void)initSDKEnvironment
{
    // 加载jsframework main.js文件
    NSString *filePath = [[NSBundle bundleForClass:self] pathForResource:@"main" ofType:@"js"];
    NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    // 将script交给initSDKEnvironment处理
    [WXSDKEngine initSDKEnvironment:script];
   .....
}
- (void)executeJSFramework:(NSString *)frameworkScript
{   
    // 断言参数是否合法
    WXAssertParam(frameworkScript);
    if (WX_SYS_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
        // 系统大于8.0加载网络
        [_jsContext evaluateScript:frameworkScript withSourceURL:[NSURL URLWithString:@"main.js"]];
    }else{
        // 小于8.0加载本地frameworkScript
        [_jsContext evaluateScript:frameworkScript];
    }
}
  • 3.在jsframework 加载完毕的前提下通过JSContext调用的jsframework的registerComponents方法加载默认组件
    3.1 注册默认的组件
+ (void)_registerDefaultComponents
{
    // 加载container组件
    [self registerComponent:@"container" withClass:NSClassFromString(@"WXDivComponent") withProperties:nil];
    // 加载div组件
    [self registerComponent:@"div" withClass:NSClassFromString(@"WXComponent") withProperties:nil];
....
}

3.2 通过WXComponentFactory工厂生产配置

    // 通过WXComponentFactory工厂生产配置
    [WXComponentFactory registerComponent:name withClass:clazz withPros:properties];
    NSMutableDictionary *dict = [WXComponentFactory componentMethodMapsWithName:name];
/*
生成的配置结构
{
    methods =     (
    );
    type = div;
}
*/

3.3 jsframework 未加载完毕通过_methodQueue 队列缓冲未注册的组件

- (void)callJSMethod:(NSString *)method args:(NSArray *)args
{   

    if (self.frameworkLoadFinished) {
        // 加载完毕直接调用JSFramework的registerComponents方法
        [self.jsBridge callJSMethod:method args:args];
    } else {
        // 如果JSFramework未加载完毕那么将配置加入到_methodQueue队列中待命
        [_methodQueue addObject:@{@"method":method, @"args":args}];
    }
}

3.4 jsframework 加载完毕通过JSContext调用的jsframework的registerComponents方法加载默认组件

- (void)registerComponents:(NSArray *)components
{
    WXAssertBridgeThread();
    // 判断配置是否合法
    if(!components) return;
    // 通过JSContext调用JSFramework的registerComponents方法注册模块
    [self callJSMethod:@"registerComponents" args:@[components]];
}

3.5 流程图

alt

  • 4.在jsframework 加载完毕的前提下通过JSContext调用的jsframework的registerModules方法加载默认模块
    4.1 注册默认的模块
+ (void)_registerDefaultModules
{
    // 注册dom模块
    [self registerModule:@"dom" withClass:NSClassFromString(@"WXDomModule")];
    // 注册navigator模块
    [self registerModule:@"navigator" withClass:NSClassFromString(@"WXNavigatorModule")];
.....
}

4.2 通过WXModuleFactory工厂生产配置

 // 通过WXModuleFactory工厂生产配置
    NSString *moduleName = [WXModuleFactory registerModule:name withClass:clazz];
    NSDictionary *dict = [WXModuleFactory moduleMethodMapsWithName:moduleName];
/*
生成的配置结构
{
    modal =     (
        addEventListener,
        removeAllEventListeners,
        alert,
        toast,
        prompt,
        confirm
    );
}

*/

4.3 jsframework 未加载完毕通过_methodQueue 队列缓冲未注册的模块

- (void)callJSMethod:(NSString *)method args:(NSArray *)args
{   
    if (self.frameworkLoadFinished) {
        // 如果jsframework加载完毕调用registerModules方法注册
        [self.jsBridge callJSMethod:method args:args];
    } else {
        // 否则加入任务队列待命
        [_methodQueue addObject:@{@"method":method, @"args":args}];
    }
}

4.4 jsframework 加载完毕通过JSContext调用的jsframework的registerModules方法加载默认组件

- (void)registerModules:(NSDictionary *)modules
{
    // 断言是否在com.taobao.weex.bridge线程
    WXAssertBridgeThread();
    // 参数错误处理
    if(!modules) return;
    // 通过JSContext调用JSFramework的registerModules方法注册模块
    [self callJSMethod:@"registerModules" args:@[modules]];
}

4.5 流程图
alt

  • 5.将默认的handlers加载给WXHandlerFactory的handlers存储起来备用
    5.1 注册默认的handler
+ (void)_registerDefaultHandlers
{
    // 默认注册WXResourceRequestHandler协议的实现
    [self registerHandler:[WXResourceRequestHandlerDefaultImpl new] withProtocol:@protocol(WXResourceRequestHandler)];
    // 默认注册WXNavigationProtocol协议的实现
    ......
}

5.2 WXHandlerFactory工厂管理handlers待用

+ (void)registerHandler:(id)handler withProtocol:(Protocol *)protocol
{
    WXAssert(handler && protocol, @"Handler or protocol for registering can not be nil.");
    WXAssertProtocol(handler, protocol);

    [[WXHandlerFactory sharedInstance].handlers setObject:handler forKey:NSStringFromProtocol(protocol)];
}
/*
生产的配置结构
{
    WXResourceRequestHandler = "<WXResourceRequestHandlerDefaultImpl: 0x6080002230a0>";
}
*/

5.3 流程图
alt

4.1.3 流程总结

alt

4.2.weex引擎加载weex页面的的过程
4.2.1总体流程图

alt

4.2.2详解

1.js页面资源挂载

2.jsBundleString 通过JSContext调用JSFramework的createInstance方法解析页面

4.3weex页面与native交互的过程、
4.3.1 核心部分分析

1.weex会调用sendTasks(id, tasks)发送消息调用WXJSCoreBridge callNativeBlock 具体实现在WXBridgeContext

 {
        args =         (
                        {
                attr =                 {
                    "data-v-294bc866" = "";
                };
                ref = "_root";
                style =                 {
                    backgroundColor = "#FFFFFF";
                    bottom = 0;
                    left = 0;
                    right = 0;
                    top = 0;
                };
                type = div;
            }
        );
        method = createBody;
        module = dom;
    }
)

2.weex会调用WXDomModule createBody方法 创建rootCSSNode将self.weexInstance.frame丢给FlexBox算法强力驱动计算布局
3.核心部分

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.native调用weex方式主要通过以下2中方式

fireEvent
callback

4.3.2 流程图

alt

4.4总结
4.4.1 核心部分分析
  • 1.JSFramework在app启动会初始化一次,多个页面都共享这一份JSFramework。
  • 2.会去注册默认的组件模块协议
  • 3.当native需要渲染页面的时候,会主动调用weex 的createInstance方法,其中code参数就是JS Bundle转换成的String。JSFramework接收到后就会开始解析,并开始sendTasks(id, tasks)。
  • 4.weex端通过sendTasks(id, tasks)发送消息给native会通过JSBridge调用OC Native方法。tasks里面会指定功能的模块名、方法名以及参数。
  • 5.native也会调用receiveTasks(id, tasks)方法,调用JS的方法主要通过fireEvent,或者callback的方式
4.4.2 流程图

alt