0.从工作原理谈起

Weex 表面上是一个客户端技术,但实际上它串联起了从本地开发、云端部署到分发的整个链路。开发者首先可在本地像编写 web 页面一样编写一个 app 的界面,然后通过命令行工具将之编译成一段 JavaScript 代码,生成一个 Weex 的 JS bundle;同时,开发者可以将生成的 JS bundle 部署至云端,然后通过网络请求或预下发的方式加载至用户的移动应用客户端;在移动应用客户端里,Weex SDK 会准备好一个 JavaScript 执行环境,并且在用户打开一个 Weex 页面时在这个执行环境中执行相应的 JS bundle,并将执行过程中产生的各种命令发送到 native 端进行界面渲染、数据存储、网络通信、调用设备功能及用户交互响应等功能;同时,如果用户希望使用浏览器访问这个界面,那么他可以在浏览器里打开一个相同的 web 页面,这个页面和移动应用使用相同的页面源代码,但被编译成适合Web展示的JS Bundle,通过浏览器里的 JavaScript 引擎及 Weex SDK 运行起来的。

上面是Weex官方的介绍。下面对Native端的原理做一下细分:

下面我们开始对Native端的解析,做一下分析。

1.WeexSDK初始化

我们新建一个Weex项目之后,通过“weex platform add ios”,可以添加iOS的工程代码。打开该工程,可以看到如下内容:

+ (void)initWeexSDK
{
[WXAppConfiguration setAppGroup:@"AliApp"];
[WXAppConfiguration setAppName:@"WeexDemo"];
[WXAppConfiguration setAppVersion:@"1.8.3"];
[WXAppConfiguration setExternalUserAgent:@"ExternalUA"]; [WXSDKEngine initSDKEnvironment]; [WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)]; #ifdef DEBUG
[WXLog setLogLevel:WXLogLevelLog];
#endif
}

该代码一般在application: didFinishLaunchingWithOptions:中进行调用,用于初始化WeexSDK,下面我们来看一下这部分的源码实现,通过查看源码,更深入的理解WeexSDK。

2.WXAppConfiguration

@interface WXAppConfiguration : NSObject

/**
* @abstract Group or organization of your app, default value is nil.
*/
+ (NSString *)appGroup;
+ (void)setAppGroup:(NSString *) appGroup; /**
* @abstract Name of your app, default is value for CFBundleDisplayName in main bundle.
*/
+ (NSString *)appName;
+ (void)setAppName:(NSString *)appName; /**
* @abstract Version of your app, default is value for CFBundleShortVersionString in main bundle.
*/
+ (NSString *)appVersion;
+ (void)setAppVersion:(NSString *)appVersion; /**
* @abstract External user agent of your app, all requests sent by weex will set the user agent on header, default value is nil.
*/
+ (NSString *)externalUserAgent;
+ (void)setExternalUserAgent:(NSString *)userAgent; /**
* @abstract JSFrameworkVersion
*/
+ (NSString *)JSFrameworkVersion;
+ (void)setJSFrameworkVersion:(NSString *)JSFrameworkVersion; /**
+ * @abstract JSFrameworkLibSize
+ */
+ (NSUInteger)JSFrameworkLibSize;
+ (void)setJSFrameworkLibSize:(NSUInteger)JSFrameworkLibSize; /*
* @abstract customizeProtocolClasses
*/
+ (NSArray*)customizeProtocolClasses;
+ (void)setCustomizeProtocolClasses:(NSArray*)customizeProtocolClasses; @end

从.h文件可以看到,该类是用来用来记录App配置信息的。所有方法都是类方法,内部实现是用单例实现的,.h用类方法是为了方便调用。

3.实质初始化

[WXSDKEngine initSDKEnvironment];

该方法都做了哪些事情呢?

+ (void)initSDKEnvironment
{
// 加载本地的main.js
NSString *filePath = [[NSBundle bundleForClass:self] pathForResource:@"native-bundle-main" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
// 初始化SDK环境
[WXSDKEngine initSDKEnvironment:script]; // 模拟器版本特殊代码
#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)( * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self connectDebugServer:@"ws://localhost:8687/debugger/0/renderer"];
}); }];
});
#endif
} + (void)initSDKEnvironment:(NSString *)script
{
// 打点记录状态
WX_MONITOR_PERF_START(WXPTInitalize)
WX_MONITOR_PERF_START(WXPTInitalizeSync) if (!script || script.length <= ) {
NSMutableString *errMsg = [NSMutableString stringWithFormat:@"[WX_KEY_EXCEPTION_SDK_INIT_JSFM_INIT_FAILED] script don't exist:%@",script];
[WXExceptionUtils commitCriticalExceptionRT:@"WX_KEY_EXCEPTION_SDK_INIT" errCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_SDK_INIT] function:@"initSDKEnvironment" exception:errMsg extParams:nil];
WX_MONITOR_FAIL(WXMTJSFramework, WX_ERR_JSFRAMEWORK_LOAD, errMsg);
return;
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 注册Components,Modules,Handlers
[self registerDefaults];
// 执行JsFramework
[[WXSDKManager bridgeMgr] executeJsFramework:script];
});
// 打点记录状态
WX_MONITOR_PERF_END(WXPTInitalizeSync) }

从源码可以看到,初始化总共做了四件事情:

  • WXMonitor监视器记录状态;
  • WXSDKEngine的初始化
  • 加载本地的main.js;
  • 模拟器WXSimulatorShortcutManager连接本地server。

3.1WXMonitor

WXMonitor是一个普通的对象,它里面只存储了一个线程安全的字典WXThreadSafeMutableDictionary。

@interface WXThreadSafeMutableDictionary<KeyType, ObjectType> : NSMutableDictionary

@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, strong) NSMutableDictionary* dict; @end

WXMonitor在整个Weex里面担任的职责是记录下各个操作的tag值和记录成功和失败的原因

在WXSDKInstance初始化之前,所有的全局的global操作都会放在WXMonitor的WXThreadSafeMutableDictionary中。当WXSDKInstance初始化之后,即WXPerformanceTag中instance以下的所有操作都会放在WXSDKInstance的performanceDict中。

3.2加载本地的main.js

main.js是经过webpack压缩之后的文件,它是Native这边的JS Framework。

3.3WXSDKEngine的初始化

+ (void)registerDefaults
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self _registerDefaultComponents];
[self _registerDefaultModules];
[self _registerDefaultHandlers];
});
}

从上面可以看到,WXSDKEngine在初始化的时候,分别注册了Components、Modules、Handlers。这是 Weex 与 Native 应用层交互最核心的部分,可以理解为“组件”。其中 Component 是为了映射 Html 的一些标签,Module 中是提供一些 Native 的方法供 Weex 调用,Handler 是一些协议的实现。

注册完 Weex 默认的“组件” 之后,注入3.2小节的那段 JS,这个时候 Vue 的标签和动作才能被 Weex 所识别和转换。

3.4执行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 *exception = [[self.jsBridge exception] toString];
NSMutableString *errMsg = [NSMutableString stringWithFormat:@"[WX_KEY_EXCEPTION_SDK_INIT_JSFM_INIT_FAILED] %@",exception];
[WXExceptionUtils commitCriticalExceptionRT:@"WX_KEY_EXCEPTION_SDK_INIT" errCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_SDK_INIT] function:@"" exception:errMsg extParams:nil];
WX_MONITOR_FAIL(WXMTJSFramework, WX_ERR_JSFRAMEWORK_EXECUTE, errMsg);
} else {
WX_MONITOR_SUCCESS(WXMTJSFramework);
//the JSFramework has been load successfully.
// 至此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]];
} if (script) {
[WXAppConfiguration setJSFrameworkLibSize:[script lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];
} //execute methods which has been stored in methodQueue temporarily.
// 执行之前缓存在_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:@"native-bundle-main.js"]];
}else{
[_jsContext evaluateScript:frameworkScript];
}
}
加载JSFramework的核心代码在这里,通过JSContext执行evaluateScript:来加载JSFramework。由于这里并没有返回值,所以加载的JSFramework的目的仅仅是声明了里面的所有方法,并没有调用。这也符合OC加载其他Framework的过程,加载只是加载到内存中,Framework里面的方法可以随时被调用,而不是一加载就调用其所有的方法。

加载完成JSFramework以后,就要开始加载之前缓存的JSService和JSMethod。JSService是在jsServiceQueue中缓存的(默认SDK里面,是没有的)。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];
} for (NSDictionary *method in _methodQueue) {
[self callJSMethod:method[@"method"] args:method[@"args"]];
} - (JSValue *)callJSMethod:(NSString *)method args:(NSArray *)args
{
WXLogDebug(@"Calling JS... method:%@, args:%@", method, args);
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 NSInteger(^WXJSCallCreateBody)(NSString *instanceId, NSDictionary *bodyData);
typedef NSInteger(^WXJSCallRemoveElement)(NSString *instanceId,NSString *ref);
typedef NSInteger(^WXJSCallMoveElement)(NSString *instanceId,NSString *ref,NSString *parentRef,NSInteger index);
typedef NSInteger(^WXJSCallUpdateAttrs)(NSString *instanceId,NSString *ref,NSDictionary *attrsData);
typedef NSInteger(^WXJSCallUpdateStyle)(NSString *instanceId,NSString *ref,NSDictionary *stylesData);
typedef NSInteger(^WXJSCallAddEvent)(NSString *instanceId,NSString *ref,NSString *event);
typedef NSInteger(^WXJSCallRemoveEvent)(NSString *instanceId,NSString *ref,NSString *event);
typedef NSInteger(^WXJSCallCreateFinish)(NSString *instanceId);
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);

上面的闭包,是OC封装暴露给JS的。对应的全局函数:

- (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];
[WXTracingManager startTracingWithInstanceId:instanceIdString ref:componentData[@"ref"] className:nil name:WXTJSCall phase:WXTracingBegin functionName:@"addElement" options:@{@"threadName":WXTJSBridgeThread,@"componentData":componentData}];
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...%@,%@,%@,%@", instanceIdString, moduleNameString, methodNameString, argsArray); NSInvocation *invocation = callNativeModuleBlock(instanceIdString, moduleNameString, methodNameString, argsArray, optionsDic);
JSValue *returnValue = [JSValue wx_valueWithReturnValueFromInvocation:invocation inContext:[JSContext currentContext]];
[WXTracingManager startTracingWithInstanceId:instanceIdString ref:nil className:nil name:moduleNameString phase:WXTracingInstant functionName:methodNameString options:nil];
return returnValue;
};
} - (void)registerCallNativeComponent:(WXJSCallNativeComponent)callNativeComponentBlock
{
_jsContext[@"callNativeComponent"] = ^void(JSValue *instanceId, JSValue *componentName, JSValue *methodName, JSValue *args, JSValue *options) {
NSString *instanceIdString = [instanceId toString];
NSString *componentNameString = [componentName toString];
NSString *methodNameString = [methodName toString];
NSArray *argsArray = [args toArray];
NSDictionary *optionsDic = [options toDictionary]; WXLogDebug(@"callNativeComponent...%@,%@,%@,%@", instanceIdString, componentNameString, methodNameString, argsArray); callNativeComponentBlock(instanceIdString, componentNameString, methodNameString, argsArray, optionsDic);
};
}

由于JS的方法的写法,多个参数是依次写在小括号里面的,和OC多个参数中间用:号隔开是不一样的,所有在暴露给JS的时候,需要把Block再包装一层。包装的4个方法如上,最后把这4个方法注入到JSContext中。

如上图,灰色的就是OC本地传入的Block,外面在包一层,变成JS的方法,注入到JSContext中。

WeexSDK源码分析(iOS)的更多相关文章

  1. iOS硬解H.264:-VideoToolboxDemo源码分析[草稿]

    来源:http://www.cnblogs.com/michaellfx/p/understanding_-VideoToolboxDemo.html iOS硬解H.264:-VideoToolbox ...

  2. iOS常用框架源码分析

    SDWebImage NSCache 类似可变字典,线程安全,使用可变字典自定义实现缓存时需要考虑加锁和释放锁 在内存不足时NSCache会自动释放存储的对象,不需要手动干预 NSCache的key不 ...

  3. jQuery 2.0.3 源码分析 事件绑定 - bind/live/delegate/on

    事件(Event)是JavaScript应用跳动的心脏,通过使用JavaScript ,你可以监听特定事件的发生,并规定让某些事件发生以对这些事件做出响应 事件的基础就不重复讲解了,本来是定位源码分析 ...

  4. [Android实例] Scroll原理-附ScrollView源码分析

    想象一下你拿着放大镜贴很近的看一副巨大的清明上河图, 那放大镜里可以看到的内容是很有限的, 而随着放大镜的上下左右移动,就可以看到不同的内容了 android中手机屏幕就相当于这个放大镜, 而看到的内 ...

  5. Go Mobile 例子 basic 源码分析

    OpenGL ES(OpenGL for Embedded Systems)是 OpenGL 三维图形API的子集,针对手机.PDA和游戏主机等嵌入式设备而设计.该API由Khronos集团定义推广, ...

  6. AFNetworking源码分析

    来源:zongmumask 链接:http://www.jianshu.com/p/8eac5b1975de 简述 在iOS开发中,与直接使用苹果框架中提供的NSURLConnection或NSURL ...

  7. YYCache 源码分析(一)

    iOS 开发中总会用到各种缓存,YYCache或许是你最好的选择.性能上有优势,用法也很简单.作者ibireme曾经对比过同类轮子:http://blog.ibireme.com/2015/10/26 ...

  8. 安卓MonkeyRunner源码分析之启动

    在工作中因为要追求完成目标的效率,所以更多是强调实战,注重招式,关注怎么去用各种框架来实现目的.但是如果一味只是注重招式,缺少对原理这个内功的了解,相信自己很难对各种框架有更深入的理解. 从几个月前开 ...

  9. Appium Server源码分析之作为Bootstrap客户端

    Appium Server拥有两个主要的功能: 它是个http服务器,它专门接收从客户端通过基于http的REST协议发送过来的命令 他是bootstrap客户端:它接收到客户端的命令后,需要想办法把 ...

随机推荐

  1. 深度学习原理与框架-递归神经网络-时间序列预测(代码) 1.csv.reader(进行csv文件的读取) 2.X.tolist(将数据转换为列表类型)

    1. csv.reader(csvfile) # 进行csv文件的读取操作 参数说明:csvfile表示已经有with oepn 打开的文件 2. X.tolist() 将数据转换为列表类型 参数说明 ...

  2. IIC 设备使用

    通过 读 / 写 IIC 设备上特定的存储空间,来使用设备提供的功能: 存储空间地址 = 设备名 + 设备地址(Slave Address) + 寄存器地址 . 注:设备地址.寄存器地址.地址中写入数 ...

  3. ViewParent 和 ViewManager

    ViewGroup 继承了 View  实现了两个接口  ViewParent 和 ViewManager 接口 ViewParent:定义了成为一个View的parent的一些“职能”,当paren ...

  4. Python利用PIL生成随机验证码图片

    安装pillow: pip install pillow PIL中的Image等模块提供了创建图片,制作图片的功能,大致的步骤就是我们利用random生成6个随机字符串,然后利用PIL将字符串绘制城图 ...

  5. linux下redis4.0.2集群部署(利用Ruby脚本命令)

    一.原生命令方式和Ruby脚本方式区别 利用Ruby脚本部署和用原生命令部署,节点准备的步骤都是一样的,节点启动后的握手,以及主从.槽分配,利用Ruby脚本一步就能完成,利用原生命令需要一步一步地执行 ...

  6. vi 操作

    vi 3种模式:1命令行模式,2插入模式,3末行模式 Vim启动后直接进入的是命令行模式,命令行模式顾名思义就是可以输入各种命令的意思, 1. 命令行下按i键进入“插入模式”,命令行下按:键进入“末行 ...

  7. Python学习—基础篇之文件操作

    文件操作 文件操作也是编程中需要熟练掌握的技能,尤其是在后台接口编写和数据分析过程中,对各种类型的文件进行操作,获取文件信息或者对信息进行存储是十分重要的.本篇博客中将主要对常见的文本格式文件和Exc ...

  8. http://ctf.bugku.com/challenges#%E9%80%86%E5%90%91%E5%85%A5%E9%97%A8:bugku--逆向入门

      文件是:   分析挺简单,主要是data urls知识点.     首先使用peid检测是否加壳,发现它居然是jpg文件.使用notepad++查看,结果如下.   嗯,百度一下子,知道了data ...

  9. [leetcode]43. Multiply Strings高精度乘法

    Given two non-negative integers num1 and num2 represented as strings, return the product of num1 and ...

  10. Java12-java语法基础(十一)继承

    Java12-java语法基础(十一)继承 一.继承 学习要求: 1. 理解继承的概念与作用 2. 掌握继承的实现机制 3.理解继承中的覆写与覆盖 4.掌握super关键字的使用 回顾: 1.对客观世 ...