前言
2016年4月21日裙戏,阿里巴巴在Qcon大會(huì)上宣布跨平臺(tái)移動(dòng)開發(fā)工具Weex開放內(nèi)測(cè)邀請(qǐng)御滩。Weex能夠完美兼顧性能與動(dòng)態(tài)性鸥拧,讓移動(dòng)開發(fā)者通過(guò)簡(jiǎn)捷的前端語(yǔ)法寫出Native級(jí)別的性能體驗(yàn),并支持iOS削解、安卓富弦、YunOS及Web等多端部署。
近一年來(lái)氛驮,ReactNative 和 Weex 這些跨平臺(tái)技術(shù)對(duì)Native開發(fā)者來(lái)說(shuō)腕柜,沖擊是巨大的。Native在開發(fā)App的時(shí)候存在一些弊端矫废,比如客戶端需要頻繁更新盏缤,iOS更新時(shí)間還要受到審核的牽制;iOS蓖扑、Android和前端同時(shí)開發(fā)同一個(gè)需求唉铜,在人員成本上消耗大;Hybrid的性能和Native相比又差了一點(diǎn)律杠。
ReactNative 和 Weex的出現(xiàn)涮雷,就是為了解決這些痛點(diǎn)的甘有。
從4月21號(hào)宣布內(nèi)測(cè)以后,短短兩周就有超過(guò)5000名開發(fā)者申請(qǐng)。
2016年6月30日阿里巴巴正式宣布Weex開源贴汪。號(hào)稱可以用Web方式,開發(fā)Native級(jí)性能體驗(yàn)的億級(jí)應(yīng)用匠心打造跨平臺(tái)移動(dòng)開發(fā)工具Weex在開源首日就登上Github趨勢(shì)榜首位诽表,截止目前為止猎塞,Weex在GitHub上的Star數(shù)已經(jīng)到達(dá)了13393了。成為中國(guó)2016年在Github上最熱門的開源項(xiàng)目之一蔓罚。
目錄
- 1.Weex概述
- 2.Weex工作原理
- 3.Weex在iOS上是如何跑起來(lái)的
- 4.關(guān)于Weex椿肩,ReactNative,JSPatch
一. Weex概述
Weex從出生那天起豺谈,仿佛就是和ReactNative是“一對(duì)”郑象。
ReactNative宣稱“Learn once, write anywhere”,而Weex宣稱“Write Once, Run Everywhere”茬末。Weex從出生那天起厂榛,就被給予了一統(tǒng)三端的厚望。ReactNative可以支持iOS丽惭、Android击奶,而Weex可以支持iOS、Android责掏、HTML5柜砾。一統(tǒng)三端就解決了前言里面說(shuō)的第二個(gè)痛點(diǎn),同時(shí)開發(fā)浪費(fèi)人員成本的問(wèn)題换衬。
Native移動(dòng)開發(fā)者只需要在本地導(dǎo)入Weex的SDK痰驱,就可以通過(guò)HTML/CSS/JavaScript網(wǎng)頁(yè)的這套編程語(yǔ)言來(lái)開發(fā)Native級(jí)別的Weex界面。這意味著可以直接用現(xiàn)有Web開發(fā)的編輯器和IDE的代碼補(bǔ)全瞳浦、提示担映、檢查等功能。從而也給前端人員開發(fā)Native端术幔,較低的開發(fā)成本和學(xué)習(xí)成本另萤。
Weex是一種輕量級(jí)、可擴(kuò)展诅挑、高性能框架四敞。集成也很方便,可以直接在HTML5頁(yè)面嵌入拔妥,也可嵌在原生UI中忿危。由于和ReactNative一樣,都會(huì)調(diào)用Native端的原生控件没龙,所以在性能上比Hybrid高出一個(gè)層次铺厨。這就解決了前言里面所說(shuō)的第三個(gè)痛點(diǎn)缎玫,性能問(wèn)題。
Weex非常輕量解滓,體積小巧赃磨,語(yǔ)法簡(jiǎn)單,方便接入和上手洼裤。ReactNative官方只允許將ReactNative基礎(chǔ)js庫(kù)和業(yè)務(wù)JS一起打成一個(gè)JS bundle邻辉,沒(méi)有提供分包的功能,所以如果想節(jié)約流量就必須制作分包打包工具腮鞍。而Weex默認(rèn)打的JS bundle只包含業(yè)務(wù)JS代碼值骇,體積小很多,基礎(chǔ)JS庫(kù)包含在Weex SDK中移国,這一點(diǎn)Weex與Facebook的React Native和微軟的Cordova相比吱瘩,Weex更加輕量,體積小巧迹缀。把Weex生成的JS bundle輕松部署到服務(wù)器端使碾,然后Push到客戶端,或者客戶端請(qǐng)求新的資源即可完成發(fā)布裹芝。如此快速的迭代就解決了前言里面說(shuō)的第一個(gè)痛點(diǎn)部逮,發(fā)布無(wú)法控制時(shí)間,
Weex中Native組件和API都可以橫向擴(kuò)展嫂易,業(yè)務(wù)方可去中心化橫向靈活化定制組件和功能模塊兄朋。并且還可以直接復(fù)用Web前端的工程化管理和監(jiān)控性能等工具。
知乎上有一個(gè)關(guān)于Weex 和 ReactNative很好的對(duì)比文章weex&ReactNative對(duì)比怜械,推薦大家閱讀颅和。
Weex在2017年2月17日正式發(fā)布v0.10.0,這個(gè)里程碑的版本開始完美的兼容Vue.js開發(fā)Weex界面缕允。
Weex又于2017年2月24 遷移至 Apache 基金會(huì)峡扩,阿里巴巴會(huì)基于 Apache 的基礎(chǔ)設(shè)施繼續(xù)迭代。并啟用了全新的 GitHub 倉(cāng)庫(kù):https://github.com/apache/incubator-weex
故以下源碼分析都基于v0.10.0這個(gè)版本障本。
二. Weex工作原理
上圖是官方給的一張?jiān)韴D教届,Weex是如何把JS打包成JS Bundle的原理本篇文章暫時(shí)不涉及。本篇文章會(huì)詳細(xì)分析Weex是如何在Native端工作的驾霜。筆者把Native端的原理再次細(xì)分案训,如下圖:
Weex可以通過(guò)自己設(shè)計(jì)的DSL,書寫.we文件或者.vue文件來(lái)開發(fā)界面粪糙,整個(gè)頁(yè)面書寫分成了3段强霎,template、style蓉冈、script城舞,借鑒了成熟的MVVM的思想轩触。
Weex在性能方面,為了盡可能的提升客戶端的性能家夺,DSL的Transformer全部都放在了服務(wù)器端實(shí)現(xiàn)脱柱,Weex會(huì)在服務(wù)器端將XML + CSS + JavaScript 代碼全部都轉(zhuǎn)換成JS Bundle。服務(wù)器將JS Bundle部署到Server上和CDN上拉馋。
Weex和React Native不同的是褐捻,Weex把JS Framework內(nèi)置在SDK里面,用來(lái)解析從服務(wù)器上下載的JS Bundle椅邓,這樣也減少了每個(gè)JS Bundle的體積,不再有React Native需要分包的問(wèn)題昧狮【澳伲客戶端請(qǐng)求完JS Bundle以后,傳給JS Framework逗鸣,JS Framework解析完成以后會(huì)輸出Json格式的Virtual DOM合住,客戶端Native只需要專心負(fù)責(zé) Virtual DOM 的解析和布局、UI 渲染撒璧。然而這一套解析透葛,布局,渲染的邏輯SDK基本實(shí)現(xiàn)了卿樱。
最后Weex支持三端一致僚害,服務(wù)器上的一份JS Bundle,通過(guò)解析繁调,實(shí)現(xiàn)iOS/Android/HTML5 三端的一致性萨蚕。
三. Weex在iOS上是如何跑起來(lái)的
經(jīng)過(guò)上一章的分析,我們知道了Weex的整體流程蹄胰,由于筆者前端知識(shí)匱乏岳遥,所以從.we或者.vue文件到JS bundle前端這部分的源碼分析本文暫時(shí)不涉及,等筆者熟悉前端以后裕寨,這塊還會(huì)再補(bǔ)上來(lái)浩蓉。
分析之前先說(shuō)明一點(diǎn),Weex的所有源碼其實(shí)已經(jīng)開源了宾袜,至于SDK的Demo里面還依賴了一個(gè)ATSDK.framework捻艳,這個(gè)是沒(méi)有開源的。ATSDK.framework這個(gè)其實(shí)是Weex性能監(jiān)控的插件试和。
就是上圖中的那個(gè)灰色的框框的插件讯泣。這個(gè)插件有些大廠有自己的APM,阿里暫時(shí)沒(méi)有開源這塊阅悍,但是對(duì)Weex所有功能是不影響的好渠。
那么接下來(lái)就詳細(xì)分析一下在iOS Native端昨稼,Weex是如何跑起來(lái)的。直接上源碼分析拳锚。
(一). Weex SDK初始化
這是Native端想把Weex跑起來(lái)的第一步假栓。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.backgroundColor = [UIColor whiteColor];
// 在這里進(jìn)行初始化SDK
[self initWeexSDK];
self.window.rootViewController = [[WXRootViewController alloc] initWithRootViewController:[self demoController]];
[self.window makeKeyAndVisible];
return YES;
}
在application: didFinishLaunchingWithOptions:函數(shù)里面初始化SDK。這里會(huì)初始化很多東西霍掺∝揖#可能有人會(huì)問(wèn)了,初始化寫在這里杆烁,還初始化這么多東西牙丽,不會(huì)卡App的啟動(dòng)時(shí)間么?帶著這個(gè)問(wèn)題繼續(xù)往下看吧兔魂。
#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:里面初始化的全部?jī)?nèi)容烤芦。我們一行一行的來(lái)解讀。
WXAppConfiguration是一個(gè)用來(lái)記錄App配置信息的單例對(duì)象析校。
@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的名字或者公司組織名构罗,默認(rèn)值為nil
*/
+ (NSString *)appGroup;
+ (void)setAppGroup:(NSString *) appGroup;
/**
* app的名字, 默認(rèn)值是main bundle里面的CFBundleDisplayName
*/
+ (NSString *)appName;
+ (void)setAppName:(NSString *)appName;
/**
* app版本信息, 默認(rèn)值是main bundle里面的CFBundleShortVersionString
*/
+ (NSString *)appVersion;
+ (void)setAppVersion:(NSString *)appVersion;
/**
* app外面用戶代理的名字, 所有Weex的請(qǐng)求頭都會(huì)設(shè)置用戶代理user agent字段,默認(rèn)值為nil
*/
+ (NSString *)externalUserAgent;
+ (void)setExternalUserAgent:(NSString *)userAgent;
/**
* JSFrameworkVersion的版本
*/
+ (NSString *)JSFrameworkVersion;
+ (void)setJSFrameworkVersion:(NSString *)JSFrameworkVersion;
/*
* 自定義customizeProtocolClasses
*/
+ (NSArray*)customizeProtocolClasses;
+ (void)setCustomizeProtocolClasses:(NSArray*)customizeProtocolClasses;
@end
注意WXAppConfiguration的所有方法都是加號(hào)的類方法智玻,內(nèi)部實(shí)現(xiàn)是用WXAppConfiguration的單例實(shí)現(xiàn)的遂唧,這里用類方法是為了我們方便調(diào)用。
接下來(lái)是初始化SDK的實(shí)質(zhì)代碼了。
[WXSDKEngine initSDKEnvironment];
關(guān)于初始化的具體實(shí)現(xiàn),見下面葱跋,里面標(biāo)注了注釋:
+ (void)initSDKEnvironment
{
// 打點(diǎn)記錄狀態(tài)
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環(huán)境
[WXSDKEngine initSDKEnvironment:script];
// 打點(diǎn)記錄狀態(tài)
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
}
這里整個(gè)SDKEnvironment的初始化分成了四個(gè)步驟,WXMonitor監(jiān)視器記錄狀態(tài)谬泌,加載本地的main.js,WXSDKEngine的初始化逻谦,模擬器WXSimulatorShortcutManager連接本地server掌实。接下來(lái)一步步的分析。
1. WXMonitor監(jiān)視器記錄狀態(tài)
WXMonitor是一個(gè)普通的對(duì)象邦马,它里面只存儲(chǔ)了一個(gè)線程安全的字典WXThreadSafeMutableDictionary贱鼻。
@interface WXThreadSafeMutableDictionary<KeyType, ObjectType> : NSMutableDictionary
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, strong) NSMutableDictionary* dict;
@end
在這個(gè)字典初始化的時(shí)候會(huì)初始化一個(gè)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滋将,就會(huì)有一個(gè)與之內(nèi)存地址向?qū)?yīng)的Concurrent的queue相對(duì)應(yīng)邻悬。
這個(gè)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這四個(gè)操作都是同步操作,用dispatch_sync保護(hù)線程安全蛾扇。
- (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這三個(gè)操作加上了dispatch_barrier_async镀首。
WXMonitor在整個(gè)Weex里面擔(dān)任的職責(zé)是記錄下各個(gè)操作的tag值和記錄成功和失敗的原因坟漱。WXMonitor封裝了各種宏來(lái)方便方法的調(diào)用。
#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]
// 上面這些宏都會(huì)分別對(duì)應(yīng)下面這些具體的方法實(shí)現(xiàn)更哄。
+ (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;
整個(gè)操作被定義成2類芋齿,一個(gè)是全局的操作,一個(gè)是具體的操作成翩。
typedef enum : NSUInteger {
// global
WXPTInitalize = 0,
WXPTInitalizeSync,
WXPTFrameworkExecute,
// instance
WXPTJSDownload,
WXPTJSCreateInstance,
WXPTFirstScreenRender,
WXPTAllRender,
WXPTBundleSize,
WXPTEnd
} WXPerformanceTag;
在WXSDKInstance初始化之前觅捆,所有的全局的global操作都會(huì)放在WXMonitor的WXThreadSafeMutableDictionary中。當(dāng)WXSDKInstance初始化之后麻敌,即WXPerformanceTag中instance以下的所有操作都會(huì)放在WXSDKInstance的performanceDict中惠拭,注意performanceDict并不是線程安全的。
舉個(gè)例子:
+ (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;
}
所有的操作都會(huì)按照時(shí)間被記錄下來(lái):
WX_MONITOR_PERF_START(WXPTInitalize)
WX_MONITOR_PERF_START(WXPTInitalizeSync)
WXThreadSafeMutableDictionary字典里面會(huì)存類似這些數(shù)據(jù):
{
0 = {
start = "146297522.903652";
};
1 = {
start = "146578019.356428";
};
}
字典里面會(huì)根據(jù)操作的tag作為key值庸论。一般WX_MONITOR_PERF_START和WX_MONITOR_PERF_END是成對(duì)出現(xiàn)的,初始化結(jié)束以后就會(huì)調(diào)用WX_MONITOR_PERF_END棒呛。最終字典里面會(huì)保存成下面的樣子:
{
0 = {
end = "148750673.312226";
start = "148484241.723654";
};
1 = {
end = "148950673.312226";
start = "148485865.699819";
};
}
WXMonitor里面還會(huì)記錄一些成功和失敗的信息:
#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)
// 上面這些宏都會(huì)分別對(duì)應(yīng)下面這些具體的方法實(shí)現(xiàn)聂示。
+ (void)monitoringPointDidSuccess:(WXMonitorTag)tag onPage:(NSString *)pageName;
+ (void)monitoringPoint:(WXMonitorTag)tag didFailWithError:(NSError *)error onPage:(NSString *)pageName;
這些函數(shù)暫時(shí)這里沒(méi)有用到,暫時(shí)先不解析了簇秒。
2. 加載本地的main.js
SDK里面會(huì)帶一個(gè)main.js鱼喉,直接打開這個(gè)文件會(huì)看到一堆經(jīng)過(guò)webpack壓縮之后的文件。這個(gè)文件的源文件在https://github.com/apache/incubator-weex/tree/master/html5目錄下趋观。對(duì)應(yīng)的入口文件是 html5/render/native/index.js
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是會(huì)被當(dāng)做入?yún)鬟f給WXSDKManager扛禽。它也就是Native這邊的js framework。
3. WXSDKEngine的初始化
WXSDKEngine的初始化就是整個(gè)SDK初始化的關(guān)鍵皱坛。
+ (void)initSDKEnvironment:(NSString *)script
{
if (!script || script.length <= 0) {
WX_MONITOR_FAIL(WXMTJSFramework, WX_ERR_JSFRAMEWORK_LOAD, @"framework loading is failure!");
return;
}
// 注冊(cè)Components编曼,Modules,Handlers
[self registerDefaults];
// 執(zhí)行JsFramework
[[WXSDKManager bridgeMgr] executeJsFramework:script];
}
總共干了兩件事情剩辟,注冊(cè)Components掐场,Modules,Handlers 和 執(zhí)行JSFramework贩猎。
先來(lái)看看是怎么注冊(cè)的熊户。
+ (void)registerDefaults
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self _registerDefaultComponents];
[self _registerDefaultModules];
[self _registerDefaultHandlers];
});
}
在WXSDKEngine初始化的時(shí)候就分別注冊(cè)了這三樣?xùn)|西,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初始化的時(shí)候會(huì)默認(rèn)注冊(cè)這23種基礎(chǔ)組件艇棕。這里就舉一個(gè)最復(fù)雜的組件WXWebComponent蝌戒,來(lái)看看它是如何被注冊(cè)的串塑。
首先需要說(shuō)明的一點(diǎn),
+ (void)registerComponent:(NSString *)name withClass:(Class)clazz
{
[self registerComponent:name withClass:clazz withProperties: @{@"append":@"tree"}];
}
registerComponent:withClass:方法和registerComponent:withClass:withProperties:方法的區(qū)別在于最后一個(gè)入?yún)⑹欠駛鰼{@"append":@"tree"}瓶颠,如果被標(biāo)記成了@"tree"拟赊,那么在syncQueue堆積了很多任務(wù)的時(shí)候,會(huì)被強(qiáng)制執(zhí)行一次layout粹淋。
所以上面23種基本組件里面吸祟,只有前5種,container桃移,div屋匕,text,image借杰,scroller过吻,list是沒(méi)有被標(biāo)記成@"tree",剩下的18種都是有可能強(qiáng)制執(zhí)行一次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注冊(cè)組件的方法
[WXComponentFactory registerComponent:name withClass:clazz withPros:properties];
// 2.遍歷出所有異步的方法
NSMutableDictionary *dict = [WXComponentFactory componentMethodMapsWithName:name];
dict[@"type"] = name;
// 3.把組件注冊(cè)到WXBridgeManager中
if (properties) {
NSMutableDictionary *props = [properties mutableCopy];
if ([dict[@"methods"] count]) {
[props addEntriesFromDictionary:dict];
}
[[WXSDKManager bridgeMgr] registerComponents:@[props]];
} else {
[[WXSDKManager bridgeMgr] registerComponents:@[dict]];
}
}
注冊(cè)組件全部都是通過(guò)WXComponentFactory完成注冊(cè)的。WXComponentFactory是一個(gè)單例绞惦。
@interface WXComponentFactory : NSObject
{
NSMutableDictionary *_componentConfigs;
NSLock *_configLock;
}
@property (nonatomic, strong) NSDictionary *properties;
@end
在WXComponentFactory中逼纸,_componentConfigs會(huì)存儲(chǔ)所有的組件配置,注冊(cè)的過(guò)程也是生成_componentConfigs的過(guò)程济蝉。
- (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];
// 如果組件已經(jīng)注冊(cè)過(guò)杰刽,會(huì)提示重復(fù)注冊(cè),并且覆蓋原先的注冊(cè)行為
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];
// 注冊(cè)類方法
[config registerMethods];
[_configLock unlock];
}
在WXComponentFactory的_componentConfigs字典中會(huì)按照組件的名字作為key王滤,WXComponentConfig作為value存儲(chǔ)各個(gè)組件的配置贺嫂。
@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中存儲(chǔ)了組件名name雁乡,類名clazz第喳,類里面的同步方法字典syncMethods和異步方法字典asyncMethods。
組件注冊(cè)這里比較關(guān)鍵的一點(diǎn)是注冊(cè)類方法踱稍。
- (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_前綴的方法渔工,那么都不算是暴露出來(lái)的方法,直接continue桥温,進(jìn)行下一輪的篩選
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;
}
// 去掉方法名里面帶的:號(hào)
NSRange range = [method rangeOfString:@":"];
if (range.location != NSNotFound) {
name = [method substringToIndex:range.location];
} else {
name = method;
}
// 最終字典里面會(huì)按照異步方法和同步方法保存到最終的方法字典里
NSMutableDictionary *methods = isSyncMethod ? _syncMethods : _asyncMethods;
[methods setObject:method forKey:name];
}
free(methodList);
currentClass = class_getSuperclass(currentClass);
}
}
這里的做法也比較常規(guī),找到對(duì)應(yīng)的類方法,判斷名字里面是否帶有“sync”來(lái)判斷方法是同步還是異步方法旺韭。這里重點(diǎn)需要解析的是組件的方法是如何轉(zhuǎn)換成類方法的暴露出去的氛谜。
Weex是通過(guò)里面通過(guò)WX_EXPORT_METHOD宏做到對(duì)外暴露類方法的。
#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宏會(huì)完全展開成下面這個(gè)樣子:
#define WX_EXPORT_METHOD(method)
+ (NSString *)wx_export_method_ __LINE__ { \
return NSStringFromSelector(method); \
}
舉個(gè)例子区端,在WXWebComponent的第52行里面寫了下面這一行代碼:
WX_EXPORT_METHOD(@selector(goBack))
那么這個(gè)宏在預(yù)編譯的時(shí)候就會(huì)被展開成下面這個(gè)樣子:
+ (NSString *)wx_export_method_52 {
return NSStringFromSelector(@selector(goBack));
}
于是乎在WXWebComponent的類方法里面就多了一個(gè)wx_export_method_52的方法值漫。由于在同一個(gè)文件里面,WX_EXPORT_METHOD宏是不允許寫在同一行的织盼,所以轉(zhuǎn)換出來(lái)的方法名字肯定不會(huì)相同杨何。但是不同類里面行數(shù)就沒(méi)有規(guī)定,行數(shù)是可能相同的沥邻,從而不同類里面可能就有相同的方法名危虱。
比如在WXScrollerComponent里面的第58行
WX_EXPORT_METHOD(@selector(resetLoadmore))
WXTextAreaComponent里面的第58行
WX_EXPORT_METHOD(@selector(focus))
這兩個(gè)是不同的組件,但是宏展開之后的方法名是一樣的唐全,這兩個(gè)不同的類的類方法埃跷,是有重名的,但是完全不會(huì)有什么影響邮利,因?yàn)楂@取類方法的時(shí)候是通過(guò)class_copyMethodList弥雹,保證這個(gè)list里面都是唯一的名字即可。
還有一點(diǎn)需要說(shuō)明的是延届,雖然用class_copyMethodList會(huì)獲取所有的類方法(+號(hào)方法)缅糟,但是可能有人疑問(wèn)了,那不通過(guò)WX_EXPORT_METHOD宏對(duì)外暴露的普通的+號(hào)方法祷愉,不是也會(huì)被篩選進(jìn)來(lái)么?
回答:是的赦颇,會(huì)被class_copyMethodList獲取到二鳄,但是這里有一個(gè)判斷條件,會(huì)避開這些不通過(guò)WX_EXPORT_METHOD宏對(duì)外暴露的普通的+號(hào)類方法媒怯。
如果不通過(guò)WX_EXPORT_METHOD宏來(lái)申明對(duì)外暴露的普通的+號(hào)類方法订讼,那么名字里面就不會(huì)帶wx_export_method_的前綴的方法,那么都不算是暴露出來(lái)的方法扇苞,上面篩選的代碼里面會(huì)直接continue欺殿,進(jìn)行下一輪的篩選,所以不必?fù)?dān)心那些普通的+號(hào)類方法會(huì)進(jìn)來(lái)干擾鳖敷。
回到WXWebComponent注冊(cè)脖苏,通過(guò)上述方法獲取完類方法之后,字典里面就存儲(chǔ)的如下信息:
methods = {
goBack = goBack;
goForward = goForward;
reload = reload;
}
這就完成了組件注冊(cè)的第一步定踱,完成了注冊(cè)配置WXComponentConfig棍潘。
組件注冊(cè)的第二步,遍歷所有的異步方法。
- (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;
}
這里依舊是調(diào)用了WXComponentFactory的方法_componentMethodMapsWithName:亦歉。這里就是遍歷出異步方法恤浪,并放入字典中,返回異步方法的字典肴楷。
還是以最復(fù)雜的WXWebComponent為例水由,這里就會(huì)返回如下的異步方法字典:
{
methods = (
goForward,
goBack,
reload
);
}
注冊(cè)組件的最后一步會(huì)在JSFrame中注冊(cè)組件。
@interface WXSDKManager ()
@property (nonatomic, strong) WXBridgeManager *bridgeMgr;
@property (nonatomic, strong) WXThreadSafeMutableDictionary *instanceDict;
@end
在WXSDKManager里面會(huì)強(qiáng)持有一個(gè)WXBridgeManager赛蔫。這個(gè)WXBridgeManager就是用來(lái)和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中會(huì)弱引用WXSDKInstance實(shí)例,是為了能調(diào)用WXSDKInstance的一些屬性和方法濒募。WXBridgeManager里面最重要的一個(gè)屬性就是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;
// 存儲(chǔ)native要即將調(diào)用js的一些方法
@property (nonatomic, strong) NSMutableDictionary *sendQueue;
// 實(shí)例的一些堆棧
@property (nonatomic, strong) WXThreadSafeMutableArray *insStack;
// 標(biāo)識(shí)JSFramework是否已經(jīng)加載完成
@property (nonatomic) BOOL frameworkLoadFinished;
// 在JSFramework加載完成之前,臨時(shí)存儲(chǔ)一些方法
@property (nonatomic, strong) NSMutableArray *methodQueue;
// 存儲(chǔ)js模板的service
@property (nonatomic, strong) NSMutableArray *jsServiceQueue;
@end
在WXBridgeContext中強(qiáng)持有了一個(gè)jsBridge瑰剃。這個(gè)就是用來(lái)和js進(jìn)行交互的Bridge齿诉。
三者的關(guān)系用圖表示出來(lái)如上圖。由于是弱引用晌姚,所以用虛的線框表示粤剧。
回到注冊(cè)的步驟中來(lái),在WXSDKEngine中調(diào)用如下方法:
[[WXSDKManager bridgeMgr] registerComponents:@[dict]];
WXBridgeManager調(diào)用registerComponents方法挥唠。
- (void)registerComponents:(NSArray *)components
{
if (!components) return;
__weak typeof(self) weakSelf = self;
WXPerformBlockOnBridgeThread(^(){
[weakSelf.bridgeCtx registerComponents:components];
});
}
最終是WXBridgeManager里面的WXBridgeContext 調(diào)用registerComponents抵恋,進(jìn)行組件的注冊(cè)。但是注冊(cè)組件的這一步是在一個(gè)特殊的線程中執(zhí)行的宝磨。
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的線程中執(zhí)行的,并非主線程唤锉。WXBridgeManager會(huì)新建一個(gè)名為@"com.taobao.weex.bridge"的jsThread線程世囊,所有的組件注冊(cè)都在這個(gè)子線程中執(zhí)行的。這個(gè)jsThread也是一個(gè)單例窿祥,全局唯一株憾。
+ (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;
}
這里就是創(chuàng)建jsThread的代碼,jsThread會(huì)把@selector(_runLoopThread)作為selector晒衩。
- (void)_runLoopThread
{
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!_stopRunning) {
@autoreleasepool {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
}
于是這里就給jsThread開啟了一個(gè)runloop嗤瞎。這里是用[NSMachPort port]的方式開啟的runloop,之后再也無(wú)法獲取到這個(gè)port了听系,而且這個(gè)runloop不是CFRunloop贝奇,所以用官方文檔上的那3個(gè)方法已經(jīng)不能停止這個(gè)runloop了,只能自己通過(guò)while的方式來(lái)停止靠胜。上述代碼是一種寫法弃秆,當(dāng)然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中注冊(cè)組件碟联,其實(shí)調(diào)用的是js的方法"registerComponents"抗愁。
這里有一個(gè)需要注意的一點(diǎn),由于是在子線程上注冊(cè)組件衡查,那么JSFramework如果沒(méi)有加載完成瘩欺,native去調(diào)用js的方法,必定調(diào)用失敗拌牲。所以需要在JSFramework加載完成之前俱饿,把native調(diào)用JS的方法都緩存起來(lái),一旦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中需要一個(gè)NSMutableArray,用來(lái)緩存在JSFramework加載完成之前土居,調(diào)用JS的方法枣购。這里是保存在_methodQueue里面。如果JSFramework加載完成擦耀,那么就會(huì)調(diào)用callJSMethod:args:方法棉圈。
- (JSValue *)callJSMethod:(NSString *)method args:(NSArray *)args
{
WXLogDebug(@"Calling JS... method:%@, args:%@", method, args);
return [[_jsContext globalObject] invokeMethod:method withArguments:args];
}
由于這些注冊(cè)的方法的定義是全局函數(shù),那么很顯然應(yīng)該在JSContext的globalObject對(duì)象上調(diào)用該方法眷蜓。(目前流程進(jìn)行到這里還看不到定義的全局函數(shù)分瘾,往后看就會(huì)看到)
還是用WXWebComponent來(lái)舉例,那么這里注冊(cè)組件的method就是@“registerComponents”吁系,args參數(shù)如下:
(
{
append = tree;
methods = (
goForward,
goBack,
reload
);
type = web;
}
)
實(shí)際上程序運(yùn)行到這里德召,并不會(huì)去執(zhí)行callJSMethod:args:,因?yàn)楝F(xiàn)在JSFramework還沒(méi)有加載完成汽纤。
注冊(cè)組件的全部流程如下:
再注冊(cè)Modules
注冊(cè)Modules的流程和上面注冊(cè)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會(huì)默認(rèn)注冊(cè)這15種基礎(chǔ)模塊。這里就以比較復(fù)雜的模塊WXWebSocketModule為例冒版,來(lái)看看它是如何被注冊(cè)的。
+ (void)registerModule:(NSString *)name withClass:(Class)clazz
{
WXAssert(name && clazz, @"Fail to register the module, please check if the parameters are correct 逞姿!");
// 1. WXModuleFactory注冊(cè)模塊
NSString *moduleName = [WXModuleFactory registerModule:name withClass:clazz];
// 2.遍歷所有同步和異步方法
NSDictionary *dict = [WXModuleFactory moduleMethodMapsWithName:moduleName];
// 3.把模塊注冊(cè)到WXBridgeManager中
[[WXSDKManager bridgeMgr] registerModules:dict];
}
注冊(cè)模塊也分3步辞嗡,第一步是在WXModuleFactory中注冊(cè)。
@interface WXModuleFactory ()
@property (nonatomic, strong) NSMutableDictionary *moduleMap;
@property (nonatomic, strong) NSLock *moduleLock;
@end
在WXModuleFactory中滞造,moduleMap會(huì)存儲(chǔ)所有的模塊的配置信息续室,注冊(cè)的過(guò)程也是生成moduleMap的過(guò)程。
- (NSString *)_registerModule:(NSString *)name withClass:(Class)clazz
{
WXAssert(name && clazz, @"Fail to register the module, please check if the parameters are correct 谒养!");
[_moduleLock lock];
// 這里需要注意的是:注冊(cè)模塊是允許同名模塊的
WXModuleConfig *config = [[WXModuleConfig alloc] init];
config.name = name;
config.clazz = NSStringFromClass(clazz);
[config registerMethods];
[_moduleMap setValue:config forKey:name];
[_moduleLock unlock];
return name;
}
整個(gè)注冊(cè)的過(guò)程就是把WXModuleConfig為value挺狰,name為key明郭,存入_moduleMap字典里。
@interface WXModuleConfig : WXInvocationConfig
@end
WXModuleConfig僅僅只是繼承自WXInvocationConfig丰泊,所以它和WXInvocationConfig是完全一樣的薯定。[config registerMethods]這個(gè)方法和注冊(cè)組件的方法是同一個(gè)方法,具體注冊(cè)流程這里就不再贅述了瞳购。
在WXModuleFactory中會(huì)記錄下一個(gè)個(gè)的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>";
}
每個(gè)WXModuleConfig中會(huì)記錄下所有的同步和異步的方法话侄。
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;
}
這里遍歷模塊的方法列表和組件的有所不同学赛。首先模塊是有默認(rèn)方法的年堆。
- (NSMutableArray*)_defaultModuleMethod
{
return [NSMutableArray arrayWithObjects:@"addEventListener",@"removeAllEventListeners", nil];
}
所有的模塊都有addEventListener和removeAllEventListeners方法。第二個(gè)不同就是模塊會(huì)遍歷所有的同步和異步方法盏浇,(組件只會(huì)遍歷異步方法)变丧。最終返回生成模塊的所有方法的字典。
以dom模塊為例绢掰,它返回的字典如下:
{
dom = (
addEventListener,
removeAllEventListeners,
addEvent,
removeElement,
updateFinish,
getComponentRect,
scrollToElement,
addRule,
updateAttrs,
addElement,
createFinish,
createBody,
updateStyle,
removeEvent,
refreshFinish,
moveElement
);
}
最后一步也是在WXBridgeManager注冊(cè)模塊痒蓬。
- (void)registerModules:(NSDictionary *)modules
{
if (!modules) return;
__weak typeof(self) weakSelf = self;
WXPerformBlockOnBridgeThread(^(){
[weakSelf.bridgeCtx registerModules:modules];
});
}
這里注冊(cè)過(guò)程和組件是完全一樣的,也是在子線程@"com.taobao.weex.bridge"的jsThread中操作的曼月。
- (void)registerModules:(NSDictionary *)modules
{
WXAssertBridgeThread();
if(!modules) return;
[self callJSMethod:@"registerModules" args:@[modules]];
}
這里調(diào)用JS的方法名變?yōu)榱薂"registerModules"谊却,入?yún)rgs就是第二步產(chǎn)生的方法字典。
args = (
{
dom = (
addEventListener,
removeAllEventListeners,
addEvent,
removeElement,
updateFinish,
getComponentRect,
scrollToElement,
addRule,
updateAttrs,
addElement,
createFinish,
createBody,
updateStyle,
removeEvent,
refreshFinish,
moveElement
);
}
)
同樣哑芹,此時(shí)模塊并不會(huì)真正的被注冊(cè)上炎辨,因?yàn)镴SFramework還沒(méi)有加載完成,這里也會(huì)被添加進(jìn)methodQueue緩存起來(lái)聪姿。
注冊(cè)模塊的全部流程如下:
最后是注冊(cè)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中默認(rèn)注冊(cè)4個(gè)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會(huì)繼續(xù)調(diào)用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也是一個(gè)單例,里面有一個(gè)線程安全的字典handlers盟榴,用來(lái)保存實(shí)例和Protocol名的映射表曹质。
WXSDKEngine初始化的最后一步就是執(zhí)行JSFramework。
[[WXSDKManager bridgeMgr] executeJsFramework:script];
WXSDKManager會(huì)調(diào)用WXBridgeManager去執(zhí)行SDK里面的main.js文件擎场。
- (void)executeJsFramework:(NSString *)script
{
if (!script) return;
__weak typeof(self) weakSelf = self;
WXPerformBlockOnBridgeThread(^(){
[weakSelf.bridgeCtx executeJsFramework:script];
});
}
WXBridgeManager通過(guò)WXBridgeContext調(diào)用executeJsFramework:方法羽德。這里方法調(diào)用也是在子線程中進(jìn)行的。
- (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;
// 執(zhí)行所有注冊(cè)的JsService
[self executeAllJsService];
// 獲取JSFramework版本號(hào)
JSValue *frameworkVersion = [self.jsBridge callJSMethod:@"getJSFMVersion" args:nil];
if (frameworkVersion && [frameworkVersion isString]) {
// 把版本號(hào)存入WXAppConfiguration中
[WXAppConfiguration setJSFrameworkVersion:[frameworkVersion toString]];
}
// 執(zhí)行之前緩存在_methodQueue數(shù)組里面的所有方法
for (NSDictionary *method in _methodQueue) {
[self callJSMethod:method[@"method"] args:method[@"args"]];
}
[_methodQueue removeAllObjects];
// 至此迅办,初始化工作算完成了宅静。
WX_MONITOR_PERF_END(WXPTInitalize);
};
}
WX_MONITOR_PERF_START是在操作之前標(biāo)記WXPTFrameworkExecute。執(zhí)行完JSFramework以后站欺,用WX_MONITOR_PERF_END標(biāo)記執(zhí)行完成姨夹。
- (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的核心代碼在這里纤垂,通過(guò)JSContext執(zhí)行evaluateScript:來(lái)加載JSFramework。由于這里并沒(méi)有返回值磷账,所以加載的JSFramework的目的僅僅是聲明了里面的所有方法峭沦,并沒(méi)有調(diào)用。這也符合OC加載其他Framework的過(guò)程够颠,加載只是加載到內(nèi)存中熙侍,F(xiàn)ramework里面的方法可以隨時(shí)被調(diào)用,而不是一加載就調(diào)用其所有的方法履磨。
加載完成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轉(zhuǎn)成NSString,所以這里直接運(yùn)行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 正要調(diào)用方法");
return [[_jsContext globalObject] invokeMethod:method withArguments:args];
}
由于_methodQueue里面裝的都是全局的js方法笑跛,所以需要調(diào)用invokeMethod: withArguments:去執(zhí)行。
當(dāng)這一切都加載完成聊品,SDK的初始化工作就基本完成了飞蹂,這里就會(huì)標(biāo)記上WXPTInitalize結(jié)束。
這里還需要說(shuō)明的是翻屈,jsBridge第一次是如何被加載進(jìn)來(lái)的陈哑。
- (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;
}
第一次進(jìn)入這個(gè)函數(shù)沒(méi)有jsBridge實(shí)例的時(shí)候,會(huì)先生成WXJSCoreBridge的實(shí)例伸眶,然后緊接著注冊(cè)全局的函數(shù)惊窖。等第二次再調(diào)用這個(gè)函數(shù)的時(shí)候,_jsBridge已經(jīng)是WXJSCoreBridge類型了厘贼,就會(huì)直接return界酒,下面的語(yǔ)句也不會(huì)再重復(fù)執(zhí)行了。
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個(gè)閉包就是OC封裝暴露給JS的4個(gè)全局函數(shù)嘴秸。
- (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...%@,%@,%@,%@", instanceIdString, moduleNameString, methodNameString, argsArray);
NSInvocation *invocation = callNativeModuleBlock(instanceIdString, moduleNameString, methodNameString, argsArray, optionsDic);
JSValue *returnValue = [JSValue wx_valueWithReturnValueFromInvocation:invocation inContext:[JSContext currentContext]];
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的方法的寫法毁欣,多個(gè)參數(shù)是依次寫在小括號(hào)里面的,和OC多個(gè)參數(shù)中間用:號(hào)隔開是不一樣的岳掐,所有在暴露給JS的時(shí)候凭疮,需要把Block再包裝一層。包裝的4個(gè)方法如上岩四,最后把這4個(gè)方法注入到JSContext中哭尝。
如上圖哥攘,灰色的就是OC本地傳入的Block剖煌,外面在包一層材鹦,變成JS的方法,注入到JSContext中耕姊。
4. 模擬器WXSimulatorShortcutManager連接本地調(diào)試工具
#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(), ^{
// 連接websocket調(diào)試器
[self connectDebugServer:@"ws://localhost:8687/debugger/0/renderer"];
});
}];
});
#endif
由于平時(shí)開發(fā)可能用到模擬器桶唐,那么調(diào)試的時(shí)候就會(huì)連接到本地的瀏覽器(Chrome,Safari)進(jìn)行調(diào)試界面茉兰。這里就是在開啟模擬的時(shí)候尤泽,啟動(dòng)瀏覽器,并且連接websocket調(diào)試器规脸。
WXSDKEngine初始化的全部流程可以大概描述如下圖:
(二). Weex 是如何讓JS調(diào)起OC原生UIView的坯约?
上一章節(jié)我們分析了WXSDKEngine是如何初始化的,那么初始化完成之后莫鸭,iOS Native客戶端是如何接收到JS的頁(yè)面并調(diào)用OC生成UIView的呢闹丐?這一章節(jié)我們來(lái)分析分析。
在分析這個(gè)問(wèn)題之前被因,先來(lái)看看AppStore上面Weex官方為我們提供的實(shí)例程序WeexPlayground的掃碼功能是怎么實(shí)現(xiàn)掃描二維碼就可以進(jìn)入到一個(gè)頁(yè)面的卿拴。
1.掃二維碼的原理
首先看一下掃碼界面的一些屬性:
@interface WXScannerVC : UIViewController <AVCaptureMetadataOutputObjectsDelegate>
@property (nonatomic, strong) AVCaptureSession * session;
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *captureLayer;
@end
這個(gè)頁(yè)面沒(méi)有額外的配置,就是一些調(diào)用攝像頭的代理梨与。
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
[_captureLayer removeFromSuperlayer];
[_session stopRunning];
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
if (metadataObjects.count > 0) {
AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex : 0 ];
[self openURL:metadataObject.stringValue];
}
}
當(dāng)掃描到二維碼以后堕花,代理會(huì)調(diào)用上面這個(gè)函數(shù),掃描出來(lái)的URL就是metadataObject.stringValue粥鞋。
- (void)openURL:(NSString*)URL
{
NSString *transformURL = URL;
NSArray* elts = [URL componentsSeparatedByString:@"?"];
if (elts.count >= 2) {
NSArray *urls = [elts.lastObject componentsSeparatedByString:@"="];
for (NSString *param in urls) {
if ([param isEqualToString:@"_wx_tpl"]) {
transformURL = [[urls lastObject] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
break;
}
}
}
NSURL *url = [NSURL URLWithString:transformURL];
if ([self remoteDebug:url]) {
return;
}
[self jsReplace:url];
WXDemoViewController * controller = [[WXDemoViewController alloc] init];
controller.url = url;
controller.source = @"scan";
NSMutableDictionary *queryDict = [NSMutableDictionary new];
if (WX_SYS_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
NSArray *queryItems = [components queryItems];
for (NSURLQueryItem *item in queryItems)
[queryDict setObject:item.value forKey:item.name];
}else {
queryDict = [self queryWithURL:url];
}
NSString *wsport = queryDict[@"wsport"] ?: @"8082";
NSURL *socketURL = [NSURL URLWithString:[NSString stringWithFormat:@"ws://%@:%@", url.host, wsport]];
controller.hotReloadSocket = [[SRWebSocket alloc] initWithURL:socketURL protocols:@[@"echo-protocol"]];
controller.hotReloadSocket.delegate = controller;
[controller.hotReloadSocket open];
[[self navigationController] pushViewController:controller animated:YES];
}
上面這段是完成的打開二維碼頁(yè)面的代碼缘挽,里面包含判斷URL的query參數(shù)的一些處理。稍微簡(jiǎn)化一下陷虎,簡(jiǎn)化成下面的樣子:
- (void)openURL:(NSString*)URL
{
// 1.獲取URL
NSString *transformURL = URL;
NSURL *url = [NSURL URLWithString:transformURL];
// 2.配置新頁(yè)面的url
WXDemoViewController * controller = [[WXDemoViewController alloc] init];
controller.url = url;
controller.source = @"scan";
// 3.連接websocket
NSString *wsport = queryDict[@"wsport"] ?: @"8082";
NSURL *socketURL = [NSURL URLWithString:[NSString stringWithFormat:@"ws://%@:%@", url.host, wsport]];
controller.hotReloadSocket = [[SRWebSocket alloc] initWithURL:socketURL protocols:@[@"echo-protocol"]];
controller.hotReloadSocket.delegate = controller;
[controller.hotReloadSocket open];
// 4.頁(yè)面跳轉(zhuǎn)
[[self navigationController] pushViewController:controller animated:YES];
}
openURL:其實(shí)就干了上面注釋說(shuō)的4件事情到踏。最重要的就是給新的界面配置了URL,至于連接websocket是為了更改.we文件或者.vue文件能及時(shí)的在手機(jī)上看見更改尚猿。最后一步就是頁(yè)面跳轉(zhuǎn)窝稿。所以掃描二維碼能打開一個(gè)新的頁(yè)面,原因只是給這個(gè)新的頁(yè)面配置了一個(gè)URL凿掂,僅此而已伴榔。
2.JS是如何調(diào)起OC原生View的
再次回到我們的主題上來(lái),JS究竟是如何調(diào)起OC原生View的庄萎?
所有的秘密都在WXSDKInstance這個(gè)類里面踪少。
@interface WXSDKInstance : NSObject
// 當(dāng)前需要渲染的viewController
@property (nonatomic, weak) UIViewController *viewController;
// Native根容器的View是完全受WXSDKInstance控制,開發(fā)者無(wú)法更改
@property (nonatomic, strong) UIView *rootView;
// 如果組件想固定rootview的frame糠涛,可以把這個(gè)屬性設(shè)置為YES援奢,當(dāng)weex進(jìn)行l(wèi)ayout的時(shí)候,就不會(huì)改變r(jià)ootview的frame了忍捡。反之設(shè)置為NO
@property (nonatomic, assign) BOOL isRootViewFrozen;
// weex bundle的scriptURL
@property (nonatomic, strong) NSURL *scriptURL;
// 父Instance
@property (nonatomic, weak) WXSDKInstance *parentInstance;
// 父Instance節(jié)點(diǎn)的引用
@property (nonatomic, weak) NSString *parentNodeRef;
// 用來(lái)標(biāo)識(shí)當(dāng)前weex instance獨(dú)一無(wú)二的ID
@property (nonatomic, strong) NSString *instanceId;
// 當(dāng)前weex instance的狀態(tài)
@property (nonatomic, assign) WXState state;
// 當(dāng)weex instance完成rootView的創(chuàng)建時(shí)的回調(diào)block
@property (nonatomic, copy) void (^onCreate)(UIView *);
// 根容器的frame改變時(shí)候的回調(diào)
@property (nonatomic, copy) void (^onLayoutChange)(UIView *);
// 當(dāng)weex instance完成渲染時(shí)的回調(diào)block
@property (nonatomic, copy) void (^renderFinish)(UIView *);
// 當(dāng)weex instance刷新完成時(shí)的回調(diào)block
@property (nonatomic, copy) void (^refreshFinish)(UIView *);
// 當(dāng)weex instance渲染失敗時(shí)的回調(diào)block
@property (nonatomic, copy) void (^onFailed)(NSError *error);
// 當(dāng)weex instance頁(yè)面滾動(dòng)時(shí)的回調(diào)block
@property (nonatomic, copy) void (^onScroll)(CGPoint contentOffset);
// 當(dāng)weex instance渲染進(jìn)行中的回調(diào)block
@property (nonatomic, copy) void (^onRenderProgress)(CGRect renderRect);
// 當(dāng)前weex instance的frame
@property (nonatomic, assign) CGRect frame;
// user存儲(chǔ)的一些Info信息
@property (nonatomic, strong) NSMutableDictionary *userInfo;
// css單元和設(shè)備像素的換算比例因子
@property (nonatomic, assign, readonly) CGFloat pixelScaleFactor;
// 是否監(jiān)測(cè)組件的渲染
@property (nonatomic, assign)BOOL trackComponent;
- (void)renderWithURL:(NSURL *)url;
- (void)renderWithURL:(NSURL *)url options:(NSDictionary *)options data:(id)data;
- (void)renderView:(NSString *)source options:(NSDictionary *)options data:(id)data;
// forcedReload為YES集漾,每次加載都會(huì)從URL重新讀取切黔,為NO,會(huì)從緩存中讀取
- (void)reload:(BOOL)forcedReload;
- (void)refreshInstance:(id)data;
- (void)destroyInstance;
- (id)moduleForClass:(Class)moduleClass;
- (WXComponent *)componentForRef:(NSString *)ref;
- (NSUInteger)numberOfComponents;
- (BOOL)checkModuleEventRegistered:(NSString*)event moduleClassName:(NSString*)moduleClassName;
- (void)fireModuleEvent:(Class)module eventName:(NSString *)eventName params:(NSDictionary*)params;
- (void)fireGlobalEvent:(NSString *)eventName params:(NSDictionary *)params;
- (NSURL *)completeURL:(NSString *)url;
@end
一個(gè)WXSDKInstance就對(duì)應(yīng)一個(gè)UIViewController具篇,所以每個(gè)Weex的頁(yè)面都有一個(gè)與之對(duì)應(yīng)的WXSDKInstance纬霞。
@property (nonatomic, strong) WXSDKInstance *instance;
WXSDKInstance主要用來(lái)渲染頁(yè)面,一般通過(guò)調(diào)用renderWithURL方法驱显。
一個(gè)Weex界面的主動(dòng)渲染的過(guò)程如下:
- (void)render
{
CGFloat width = self.view.frame.size.width;
[_instance destroyInstance];
_instance = [[WXSDKInstance alloc] init];
_instance.viewController = self;
_instance.frame = CGRectMake(self.view.frame.size.width-width, 0, width, _weexHeight);
__weak typeof(self) weakSelf = self;
_instance.onCreate = ^(UIView *view) {
[weakSelf.weexView removeFromSuperview];
weakSelf.weexView = view;
[weakSelf.view addSubview:weakSelf.weexView];
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, weakSelf.weexView);
};
_instance.onFailed = ^(NSError *error) {
};
_instance.renderFinish = ^(UIView *view) {
[weakSelf updateInstanceState:WeexInstanceAppear];
};
_instance.updateFinish = ^(UIView *view) {
};
if (!self.url) {
WXLogError(@"error: render url is nil");
return;
}
NSURL *URL = [self testURL: [self.url absoluteString]];
NSString *randomURL = [NSString stringWithFormat:@"%@%@random=%d",URL.absoluteString,URL.query?@"&":@"?",arc4random()];
[_instance renderWithURL:[NSURL URLWithString:randomURL] options:@{@"bundleUrl":URL.absoluteString} data:nil];
}
由于WXSDKInstance是支持實(shí)時(shí)刷新诗芜,所以在創(chuàng)建的時(shí)候需要先銷毀掉原來(lái)的,再創(chuàng)建一個(gè)新的埃疫。
WXSDKInstance支持設(shè)置各種狀態(tài)時(shí)候的回調(diào)callback函數(shù)伏恐,具體支持哪些狀態(tài),可以看上面WXSDKInstance的定義栓霜。
Weex支持從本地加載JS脐湾,也支持從服務(wù)器加載JS。如果從本地加載叙淌,那么可以用下面的方法秤掌,從本地加載一個(gè)JSBundle。
- (void)loadLocalBundle:(NSURL *)url
{
NSURL * localPath = nil;
NSMutableArray * pathComponents = nil;
if (self.url) {
pathComponents =[NSMutableArray arrayWithArray:[url.absoluteString pathComponents]];
[pathComponents removeObjectsInRange:NSRangeFromString(@"0 3")];
[pathComponents replaceObjectAtIndex:0 withObject:@"bundlejs"];
NSString *filePath = [NSString stringWithFormat:@"%@/%@",[NSBundle mainBundle].bundlePath,[pathComponents componentsJoinedByString:@"/"]];
localPath = [NSURL fileURLWithPath:filePath];
}else {
NSString *filePath = [NSString stringWithFormat:@"%@/bundlejs/index.js",[NSBundle mainBundle].bundlePath];
localPath = [NSURL fileURLWithPath:filePath];
}
NSString *bundleUrl = [NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/bundlejs/",[NSBundle mainBundle].bundlePath]].absoluteString;
[_instance renderWithURL:localPath options:@{@"bundleUrl":bundleUrl} data:nil];
}
最后渲染頁(yè)面就是通過(guò)調(diào)用renderWithURL:options:data:做到的鹰霍。
- (void)renderWithURL:(NSURL *)url options:(NSDictionary *)options data:(id)data
{
if (!url) {
WXLogError(@"Url must be passed if you use renderWithURL");
return;
}
WXResourceRequest *request = [WXResourceRequest requestWithURL:url resourceType:WXResourceTypeMainBundle referrer:@"" cachePolicy:NSURLRequestUseProtocolCachePolicy];
[self _renderWithRequest:request options:options data:data];
}
在WXSDKInstance調(diào)用renderWithURL:options:data:方法的時(shí)候闻鉴,會(huì)生成一個(gè)WXResourceRequest。NSMutableURLRequest定義如下:
@interface WXResourceRequest : NSMutableURLRequest
@property (nonatomic, strong) id taskIdentifier;
@property (nonatomic, assign) WXResourceType type;
@property (nonatomic, strong) NSString *referrer;
@property (nonatomic, strong) NSString *userAgent;
@end
WXResourceRequest其實(shí)也就是對(duì)NSMutableURLRequest的一層封裝茂洒。
下面來(lái)分析一下最核心的函數(shù)renderWithURL:options:data:(以下的代碼實(shí)現(xiàn)在源碼的基礎(chǔ)上略有刪減孟岛,源碼太長(zhǎng),刪減以后并不影響閱讀)
- (void)_renderWithRequest:(WXResourceRequest *)request options:(NSDictionary *)options data:(id)data;
{
NSURL *url = request.URL;
_scriptURL = url;
_options = options;
_jsData = data;
NSMutableDictionary *newOptions = [options mutableCopy] ?: [NSMutableDictionary new];
WX_MONITOR_INSTANCE_PERF_START(WXPTJSDownload, self);
__weak typeof(self) weakSelf = self;
_mainBundleLoader = [[WXResourceLoader alloc] initWithRequest:request];
// 請(qǐng)求完成的回調(diào)
_mainBundleLoader.onFinished = ^(WXResourceResponse *response, NSData *data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if ([response isKindOfClass:[NSHTTPURLResponse class]] && ((NSHTTPURLResponse *)response).statusCode != 200) {
NSError *error = [NSError errorWithDomain:WX_ERROR_DOMAIN
code:((NSHTTPURLResponse *)response).statusCode
userInfo:@{@"message":@"status code error."}];
if (strongSelf.onFailed) {
strongSelf.onFailed(error);
}
return ;
}
if (!data) {
if (strongSelf.onFailed) {
strongSelf.onFailed(error);
}
return;
}
NSString *jsBundleString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (!jsBundleString) {
return;
}
[strongSelf _renderWithMainBundleString:jsBundleString];
};
// 請(qǐng)求失敗的回調(diào)
_mainBundleLoader.onFailed = ^(NSError *loadError) {
if (weakSelf.onFailed) {
weakSelf.onFailed(loadError);
}
};
[_mainBundleLoader start];
}
上面代碼只要就是干了2件事情督勺,第一步渠羞,生成了WXResourceLoader,并設(shè)置了它的onFinished和onFailed回調(diào)智哀。第二步調(diào)用了start方法次询。
在WXSDKInstance中強(qiáng)持有了一個(gè)WXResourceLoader,WXResourceLoader的定義如下:
@interface WXResourceLoader : NSObject
@property (nonatomic, strong) WXResourceRequest *request;
@property (nonatomic, copy) void (^onDataSent)(unsigned long long /* bytesSent */, unsigned long long /* totalBytesToBeSent */);
@property (nonatomic, copy) void (^onResponseReceived)(const WXResourceResponse *);
@property (nonatomic, copy) void (^onDataReceived)(NSData *);
@property (nonatomic, copy) void (^onFinished)(const WXResourceResponse *, NSData *);
@property (nonatomic, copy) void (^onFailed)(NSError *);
- (instancetype)initWithRequest:(WXResourceRequest *)request;
- (void)start;
- (void)cancel:(NSError **)error;
@end
WXResourceLoader里面含有一個(gè)WXResourceRequest瓷叫,所以WXResourceRequest也可以看出對(duì)網(wǎng)絡(luò)請(qǐng)求的封裝屯吊,并且提供了5種不同狀態(tài)的callback回調(diào)函數(shù)。
- (void)start
{
if ([_request.URL isFileURL]) {
[self _handleFileURL:_request.URL];
return;
}
id<WXResourceRequestHandler> requestHandler = [WXHandlerFactory handlerForProtocol:@protocol(WXResourceRequestHandler)];
if (requestHandler) {
[requestHandler sendRequest:_request withDelegate:self];
} else if ([WXHandlerFactory handlerForProtocol:NSProtocolFromString(@"WXNetworkProtocol")]){
// deprecated logic
[self _handleDEPRECATEDNetworkHandler];
} else {
WXLogError(@"No resource request handler found!");
}
}
在調(diào)用了WXResourceLoader的start方法以后摹菠,會(huì)先判斷是不是本地的url盒卸,如果是本地的文件,那么就直接開始加載次氨。
- (void)_handleFileURL:(NSURL *)url
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *fileData = [[NSFileManager defaultManager] contentsAtPath:[url path]];
if (self.onFinished) {
self.onFinished([WXResourceResponse new], fileData);
}
});
}
本地文件就直接回調(diào)onFinished函數(shù)蔽介。
如果不是本地的文件,就開始發(fā)起網(wǎng)絡(luò)請(qǐng)求,請(qǐng)求服務(wù)器端的js文件。
- (void)sendRequest:(WXResourceRequest *)request withDelegate:(id<WXResourceRequestDelegate>)delegate
{
if (!_session) {
NSURLSessionConfiguration *urlSessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
if ([WXAppConfiguration customizeProtocolClasses].count > 0) {
NSArray *defaultProtocols = urlSessionConfig.protocolClasses;
urlSessionConfig.protocolClasses = [[WXAppConfiguration customizeProtocolClasses] arrayByAddingObjectsFromArray:defaultProtocols];
}
_session = [NSURLSession sessionWithConfiguration:urlSessionConfig
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
_delegates = [WXThreadSafeMutableDictionary new];
}
NSURLSessionDataTask *task = [_session dataTaskWithRequest:request];
request.taskIdentifier = task;
[_delegates setObject:delegate forKey:task];
[task resume];
}
這里的網(wǎng)絡(luò)請(qǐng)求就是普通的正常的NSURLSession網(wǎng)絡(luò)請(qǐng)求岂昭。
如果成功,最終都會(huì)執(zhí)行onFinished的回調(diào)函數(shù)。
_mainBundleLoader.onFinished = ^(WXResourceResponse *response, NSData *data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if ([response isKindOfClass:[NSHTTPURLResponse class]] && ((NSHTTPURLResponse *)response).statusCode != 200) {
NSError *error = [NSError errorWithDomain:WX_ERROR_DOMAIN
code:((NSHTTPURLResponse *)response).statusCode
userInfo:@{@"message":@"status code error."}];
if (strongSelf.onFailed) {
strongSelf.onFailed(error);
}
return ;
}
if (!data) {
NSString *errorMessage = [NSString stringWithFormat:@"Request to %@ With no data return", request.URL];
WX_MONITOR_FAIL_ON_PAGE(WXMTJSDownload, WX_ERR_JSBUNDLE_DOWNLOAD, errorMessage, strongSelf.pageName);
if (strongSelf.onFailed) {
strongSelf.onFailed(error);
}
return;
}
NSString *jsBundleString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"下載下來(lái)的 jsBundleString = %@",jsBundleString);
if (!jsBundleString) {
WX_MONITOR_FAIL_ON_PAGE(WXMTJSDownload, WX_ERR_JSBUNDLE_STRING_CONVERT, @"data converting to string failed.", strongSelf.pageName)
return;
}
WX_MONITOR_SUCCESS_ON_PAGE(WXMTJSDownload, strongSelf.pageName);
WX_MONITOR_INSTANCE_PERF_END(WXPTJSDownload, strongSelf);
[strongSelf _renderWithMainBundleString:jsBundleString];
};
在onFinished的回調(diào)中碗殷,還會(huì)有3種錯(cuò)誤判斷,status code error闰蛔,no data return悟泵,data converting to string failed。
NSString *jsBundleString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[strongSelf _renderWithMainBundleString:jsBundleString];
如果一切正常挑童,那么在onFinished的回調(diào)中其實(shí)就是拿到j(luò)sBundleString累铅,并執(zhí)行渲染操作。
- (void)_renderWithMainBundleString:(NSString *)mainBundleString
{
//以下代碼有刪減站叼,去除了一些錯(cuò)誤判斷娃兽,但是不影響閱讀
NSMutableDictionary *dictionary = [_options mutableCopy];
//生成WXRootView
WXPerformBlockOnMainThread(^{
_rootView = [[WXRootView alloc] initWithFrame:self.frame];
_rootView.instance = self;
if(self.onCreate) {
self.onCreate(_rootView);
}
});
// 再次注冊(cè)默認(rèn)的模塊modules、組件components尽楔、handlers投储,以確保在創(chuàng)建instance之前它們都被注冊(cè)了
[WXSDKEngine registerDefaults];
// 開始createInstance
[[WXSDKManager bridgeMgr] createInstance:self.instanceId template:mainBundleString options:dictionary data:_jsData];
}
這里WXSDKEngine還會(huì)重新再次注冊(cè)一遍模塊modules、組件components阔馋、handlers玛荞,以確保在創(chuàng)建instance之前它們都被注冊(cè)了。
- (void)createInstance:(NSString *)instance
template:(NSString *)temp
options:(NSDictionary *)options
data:(id)data
{
if (!instance || !temp) return;
if (![self.instanceIdStack containsObject:instance]) {
if ([options[@"RENDER_IN_ORDER"] boolValue]) {
[self.instanceIdStack addObject:instance];
} else {
[self.instanceIdStack insertObject:instance atIndex:0];
}
}
__weak typeof(self) weakSelf = self;
WXPerformBlockOnBridgeThread(^(){
[weakSelf.bridgeCtx createInstance:instance
template:temp
options:options
data:data];
});
}
WXSDKManager中會(huì)調(diào)用createInstance:template:options:data:方法呕寝,這個(gè)方法也必須在JSThread中執(zhí)行勋眯。
- (void)createInstance:(NSString *)instance
template:(NSString *)temp
options:(NSDictionary *)options
data:(id)data
{
if (![self.insStack containsObject:instance]) {
if ([options[@"RENDER_IN_ORDER"] boolValue]) {
[self.insStack addObject:instance];
} else {
[self.insStack insertObject:instance atIndex:0];
}
}
//create a sendQueue bind to the current instance
NSMutableArray *sendQueue = [NSMutableArray array];
[self.sendQueue setValue:sendQueue forKey:instance];
NSArray *args = nil;
if (data){
args = @[instance, temp, options ?: @{}, data];
} else {
args = @[instance, temp, options ?: @{}];
}
[self callJSMethod:@"createInstance" args:args];
}
最終還是WXJSCoreBridge里面的JSContext調(diào)用
[[_jsContext globalObject] invokeMethod:method withArguments:args];
調(diào)用JS的"createInstance"方法。從此處開始下梢,就開始和JSFramework進(jìn)行相互調(diào)用了客蹋。
在舉例之前,我們先把前面的流程畫圖總結(jié)一下:
接下來(lái)用一個(gè)例子來(lái)說(shuō)明JS是如何調(diào)用起OC原生的View的孽江。
先用JS寫一個(gè)頁(yè)面:
<template>
<div class="container">
<image src="http://9.pic.paopaoche.net/up/2016-7/201671315341.png" class="pic" onclick="picClick"></image>
<text class="text">{{title}}</text>
</div>
</template>
<style>
.container{
align-items: center;
}
.pic{
width: 200px;
height: 200px;
}
.text{
font-size: 40px;
color: black;
}
</style>
<script>
module.exports = {
data:{
title:'Hello World',
toggle:false,
},
ready:function(){
console.log('this.title == '+this.title);
this.title = 'hello Weex';
console.log('this.title == '+this.title);
},
methods:{
picClick: function () {
this.toggle = !this.toggle;
if(this.toggle){
this.title = '圖片被點(diǎn)擊';
}else{
this.title = 'Hello Weex';
}
}
}
}
</script>
這個(gè)頁(yè)面跑起來(lái)長(zhǎng)下面這個(gè)樣子:
上面是我的.we源文件讶坯,經(jīng)過(guò)Weex編譯以后,就變成了index.js岗屏,里面的代碼如下:
// { "framework": "Weex" }
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
var __weex_template__ = __webpack_require__(1)
var __weex_style__ = __webpack_require__(2)
var __weex_script__ = __webpack_require__(3)
__weex_define__('@weex-component/916f9ecb075bbff1f4ea98389a4bb514', [], function(__weex_require__, __weex_exports__, __weex_module__) {
__weex_script__(__weex_module__, __weex_exports__, __weex_require__)
if (__weex_exports__.__esModule && __weex_exports__.default) {
__weex_module__.exports = __weex_exports__.default
}
__weex_module__.exports.template = __weex_template__
__weex_module__.exports.style = __weex_style__
})
__weex_bootstrap__('@weex-component/916f9ecb075bbff1f4ea98389a4bb514',undefined,undefined)
/***/ },
/* 1 */
/***/ function(module, exports) {
module.exports = {
"type": "div",
"classList": [
"container"
],
"children": [
{
"type": "image",
"attr": {
"src": "http://9.pic.paopaoche.net/up/2016-7/201671315341.png"
},
"classList": [
"pic"
],
"events": {
"click": "picClick"
}
},
{
"type": "text",
"classList": [
"text"
],
"attr": {
"value": function () {return this.title}
}
}
]
}
/***/ },
/* 2 */
/***/ function(module, exports) {
module.exports = {
"container": {
"alignItems": "center"
},
"pic": {
"width": 200,
"height": 200
},
"text": {
"fontSize": 40,
"color": "#000000"
}
}
/***/ },
/* 3 */
/***/ function(module, exports) {
module.exports = function(module, exports, __weex_require__){'use strict';
module.exports = {
data: function () {return {
title: 'Hello World',
toggle: false
}},
ready: function ready() {
console.log('this.title == ' + this.title);
this.title = 'hello Weex';
console.log('this.title == ' + this.title);
},
methods: {
picClick: function picClick() {
this.toggle = !this.toggle;
if (this.toggle) {
this.title = '圖片被點(diǎn)擊';
} else {
this.title = 'Hello Weex';
}
}
}
};}
/* generated by weex-loader */
/***/ }
/******/ ]);
看上去一堆代碼闽巩,實(shí)際上仔細(xì)看看,就能看出門道担汤。
(function(modules) { // webpackBootstrap
…… ……
}
這段代碼是自動(dòng)加的涎跨,暫時(shí)不管。然后下面有4段代碼崭歧,開頭都分別編了序號(hào)隅很,0,1,2叔营,3屋彪。1,2绒尊,3段代碼就是分別對(duì)應(yīng)<template>畜挥,<style>,<script>婴谱。上述這段代碼就是從服務(wù)器請(qǐng)求下來(lái)的代碼蟹但。
那服務(wù)器拿到JS以后,OC會(huì)調(diào)用JS的方法createInstance(id, code, config, data)方法谭羔。
args:(
0,
“(這里是網(wǎng)絡(luò)上下載的JS华糖,由于太長(zhǎng)了,省略)”,
{
bundleUrl = "http://192.168.31.117:8081/HelloWeex.js";
debug = 1;
}
)
接著會(huì)在JSFramework里面執(zhí)行一些轉(zhuǎn)換的操作:
[JS Framework] create an Weex@undefined instance from undefined ?[;
[JS Framework] Intialize an instance with: undefined ?[;
[JS Framework] define a component @weex-component/916f9ecb075bbff1f4ea98389a4bb514 ?[;
[JS Framework] bootstrap for @weex-component/916f9ecb075bbff1f4ea98389a4bb514 ?[;
[JS Framework] "init" lifecycle in Vm(916f9ecb075bbff1f4ea98389a4bb514) ?[;
[JS Framework] "created" lifecycle in Vm(916f9ecb075bbff1f4ea98389a4bb514) ?[;
[JS Framework] compile native component by {"type":"div","classList":["container"],"children":[{"type":"image","attr":{"src":"http://9.pic.paopaoche.net/up/2016-7/201671315341.png"},"classList":["pic"],"events":{"click":"picClick"}},{"type":"text","classList":["text"],"attr":{}}]} ?[;
[JS Framework] compile to create body for div ?[;
[JS Framework] compile to append single node for {"ref":"_root","type":"div","attr":{},"style":{"alignItems":"center"}}
接下來(lái)JSFramework就會(huì)調(diào)用OC的callNative方法瘟裸。調(diào)用dom模塊的createBody方法客叉,創(chuàng)建rootView。參數(shù)如下:
(
{
args = (
{
attr = {
};
ref = "_root";
style = {
alignItems = center;
};
type = div;
}
);
method = createBody;
module = dom;
}
)
創(chuàng)建好rootView以后话告,接著要繼續(xù)添加View了兼搏。
[JS Framework] compile native component by {"type":"image","attr":{"src":"http://9.pic.paopaoche.net/up/2016-7/201671315341.png"},"classList":["pic"],"events":{"click":"picClick"}} ?[;
[JS Framework] compile to create element for image ?[;
[JS Framework] compile to append single node for {"ref":"3","type":"image","attr":{"src":"http://9.pic.paopaoche.net/up/2016-7/201671315341.png"},"style":{"width":200,"height":200},"event":["click"]}
JSFramework繼續(xù)調(diào)用OC的callAddElement方法添加View。參數(shù)如下:
{
attr = {
src = "http://9.pic.paopaoche.net/up/2016-7/201671315341.png";
};
event = (
click
);
ref = 3;
style = {
height = 200;
width = 200;
};
type = image;
}
UIImage添加完成以后沙郭,再接著添加UILabel向族。
[JS Framework] compile native component by {"type":"text","classList":["text"],"attr":{}} ?[;
[JS Framework] compile to create element for text ?[;
[JS Framework] compile to append single node for {"ref":"4","type":"text","attr":{"value":"Hello World"},"style":{"fontSize":40,"color":"#000000"}}
JSFramework繼續(xù)調(diào)用OC的callAddElement方法添加View。參數(shù)如下:
{
attr = {
value = "Hello World";
};
ref = 4;
style = {
color = "#000000";
fontSize = 40;
};
type = text;
}
當(dāng)ready以后:
[JS Framework] "ready" lifecycle in Vm(916f9ecb075bbff1f4ea98389a4bb514)
JSFramework繼續(xù)調(diào)用OC的callNative方法棠绘,參數(shù)如下:
(
{
args = (
4,
{
value = "hello Weex";
}
);
method = updateAttrs;
module = dom;
}
)
至此件相,所有的布局已經(jīng)完成。JSFramework會(huì)繼續(xù)調(diào)用OC的callNative方法氧苍。
(
{
args = (
);
method = createFinish;
module = dom;
}
)
到此為止夜矗,所有的View都已經(jīng)創(chuàng)建完成了。最終整個(gè)布局如下:
{layout: {width: 414, height: 672, top: 0, left: 0}, flexDirection: 'column', alignItems: 'stretch', flex: 0, width: 414, height: 672, left: 0, top: 0, children: [
{_root:div layout: {width: 414, height: 672, top: 0, left: 0}, flexDirection: 'column', alignItems: 'center', flex: 0, width: 414, height: 672, children: [
{3:image layout: {width: 110.4, height: 110.4, top: 0, left: 151.8}, flexDirection: 'column', alignItems: 'stretch', flex: 0, width: 110.4, height: 110.4, },
{4:text layout: {width: 107.333, height: 26.6667, top: 110.4, left: 153.333}, flexDirection: 'column', alignItems: 'stretch', flex: 0, },
]},
]}
從最終的layout來(lái)看让虐,我們可以看出紊撕,每一個(gè)module,component都有其對(duì)應(yīng)的獨(dú)一無(wú)二的id赡突。
接著下一步操作是WXImageComponent更新圖片对扶。更新結(jié)束以后,整個(gè)Render就算徹底完成了惭缰。
JSFramework在整個(gè)過(guò)程中扮演的角色是根據(jù)輸入的JSBundle浪南,不斷的輸出Json格式的Virtual DOM,然后通過(guò)JSCore調(diào)用OC原生方法漱受,生成View络凿。
上面這個(gè)例子中,JSFramework的工作原理基本就展現(xiàn)出來(lái)了。大體流程如下圖:
接下來(lái)詳細(xì)總結(jié)一下JSFramework在整個(gè)Native端是如何工作的絮记。
首先JSFramework的初始化只會(huì)在App啟動(dòng)時(shí)初始化一次摔踱,多個(gè)頁(yè)面都共享這一份JSFramework。這個(gè)設(shè)計(jì)也提高了Weex所有頁(yè)面的打開速度怨愤, JS Framework 的啟動(dòng)過(guò)程幾百毫秒派敷,相當(dāng)于每個(gè)頁(yè)面打開的時(shí)候,這幾百毫秒都被節(jié)省下來(lái)了撰洗。
雖然JSFramework全局只有一個(gè)篮愉,那么Weex是如何避免多個(gè)Weex在同一個(gè)JS Runtime里面相互互不影響?Weex采取了2方面的措施了赵,一是要求每個(gè)Weex頁(yè)面都必須要?jiǎng)?chuàng)建一個(gè)全局唯一的 instance ID,通過(guò)這個(gè)ID直接能對(duì)應(yīng)一個(gè)Weex頁(yè)面甸赃。二是JS與Native進(jìn)行相互調(diào)用的時(shí)候柿汛,每個(gè)方法都要求第一個(gè)參數(shù)是ID。比如createInstance(id, code, config, data)埠对,sendTasks(id, tasks)络断,receiveTasks(id, tasks)。這樣不同頁(yè)面的狀態(tài)就被隔離到了不同的閉包中了项玛,這樣就做到了相互不影響貌笨。
當(dāng)Native需要渲染頁(yè)面的時(shí)候,會(huì)主動(dòng)調(diào)用createInstance(id, code, config, data)方法襟沮,其中code參數(shù)就是JS Bundle轉(zhuǎn)換成的String锥惋。JSFramework接收到了這段入?yún)⒁院螅蜁?huì)開始解析开伏,并開始sendTasks(id, tasks)膀跌。
sendTasks(id, tasks)會(huì)通過(guò)JSBridge調(diào)用OC Native方法。tasks里面會(huì)指定功能的模塊名固灵、方法名以及參數(shù)捅伤。比如:
sendTasks(id, [{ module: 'dom', method: 'removeElement', args: [elementRef]}])
這里就會(huì)調(diào)用之前注冊(cè)到JSContext的OC方法。
- 客戶端也會(huì)調(diào)用receiveTasks(id, tasks)方法巫玻,調(diào)用JS的方法丛忆。receiveTasks 中有兩種方式,一種是fireEvent仍秤,對(duì)應(yīng)的是客戶端在某個(gè)DOM元素上觸發(fā)的事件熄诡,比如fireEvent(titleElementRef, 'click', eventObject);另一種則是callback诗力,即前面功能模塊調(diào)用之后產(chǎn)生的回調(diào)粮彤,比如我們通過(guò)fetch接口向Native端發(fā)送一個(gè) HTTP 請(qǐng)求,并設(shè)置了一個(gè)回調(diào)函數(shù),這個(gè)時(shí)候导坟,先在JS端為這個(gè)回調(diào)函數(shù)生成一個(gè)callbackID屿良,比如字符串 "123",這個(gè)是發(fā)送給Native端的是這個(gè)callbackID惫周,當(dāng)請(qǐng)求結(jié)束之后尘惧,native需要把請(qǐng)求結(jié)果返還給JS Runtime,為了能夠前后對(duì)得上递递,這個(gè)回調(diào)最終會(huì)成為類似 callback(callbackID, result) 的格式喷橙。
四.關(guān)于Weex,ReactNative登舞,JSPatch
這一章本來(lái)是不在這個(gè)文章之中的贰逾,但是由于近期蘋果審核,帶來(lái)了一些審核風(fēng)波菠秒,于是打算在這里稍微提提疙剑。
在各位讀者看到這篇文章的時(shí)候,純的ReactNative和純的Weex的項(xiàng)目已經(jīng)可以完美通過(guò)審核了践叠,JSPatch依舊處于被封殺的狀態(tài)言缤。
既然本篇文章分析了Weex的工作原理,那么就稍微談?wù)凴N禁灼,Weex和JSpatch的區(qū)別管挟。
首先他們?nèi)叨际腔贘S來(lái)進(jìn)行熱修復(fù)的,但是RN弄捕,Weex和JSPatch有一個(gè)最大的不同是僻孝,如果Native沒(méi)有提供可以供JS調(diào)用的方法接口的話,那么在RN和Weex界面怎么也無(wú)法實(shí)現(xiàn)Native的一些方法的守谓。
但是JSPatch不同皮璧,雖然它也是一套基于JSCore的bridge,但是它是基于Runtime的分飞,基于OC的Runtime悴务,可以實(shí)現(xiàn)各種需求,即使預(yù)先Native沒(méi)有暴露出來(lái)的接口譬猫,都可以添加方法實(shí)現(xiàn)需求讯檐,也可以更改已經(jīng)實(shí)現(xiàn)的方法。
從熱更新的能力來(lái)看染服,RN和Weex的能力僅僅只是中等能力别洪,而JSPatch是幾乎無(wú)所不能,Runtime都實(shí)現(xiàn)的柳刮,它都能實(shí)現(xiàn)挖垛。
所以從熱更新的能力上看痒钝,RN和Weex都不能改變Native原生代碼,也無(wú)法動(dòng)態(tài)調(diào)用Native系統(tǒng)私有API痢毒。所以蘋果審核允許RN和Weex通過(guò)送矩。
最后
本篇文章只講述了Weex是如何在iOS Native端跑起來(lái)的原理,但是關(guān)于Weex其實(shí)還有很多沒(méi)有解釋哪替,比如說(shuō)在Vue.js頁(yè)面更改了一個(gè)頁(yè)面元素栋荸,是怎么能讓Native頁(yè)面及時(shí)的變更?Weex的頁(yè)面是怎么通過(guò)FlexBox算法進(jìn)行渲染的凭舶?前端頁(yè)面是如何打包成JS bundle的晌块?.we和.vue文件是怎么通過(guò)DSL被翻譯的?如何利用JS的Runtime寫一些強(qiáng)大的JSService帅霜?webpackBootstrap和weex-loader是如何生成最終的JS代碼的匆背,中間有哪些優(yōu)化?……
以上的這些問(wèn)題都會(huì)在接下來(lái)一系列的Weex文章里面一一詳解身冀,希望大家多多指點(diǎn)钝尸!
GitHub Repo:Halfrost-Field
Follow: halfrost · GitHub
Source: https://halfrost.com/weex_ios/
Weex 源碼解析系列文章:
Weex 是如何在 iOS 客戶端上跑起來(lái)的
由 FlexBox 算法強(qiáng)力驅(qū)動(dòng)的 Weex 布局引擎
Weex 事件傳遞的那些事兒
Weex 中別具匠心的 JS Framework
iOS 開發(fā)者的 Weex 偽最佳實(shí)踐指北