Weex 是如何在 iOS 客戶端上跑起來(lái)的

前言

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端是如何工作的絮记。

  1. 首先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)了撰洗。

  2. 雖然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)就被隔離到了不同的閉包中了项玛,這樣就做到了相互不影響貌笨。

  3. 當(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)膀跌。

  4. 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方法。

  1. 客戶端也會(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í)踐指北


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市闽铐,隨后出現(xiàn)的幾起案子蝶怔,更是在濱河造成了極大的恐慌奶浦,老刑警劉巖兄墅,帶你破解...
    沈念sama閱讀 219,366評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異澳叉,居然都是意外死亡隙咸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門成洗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)五督,“玉大人,你說(shuō)我怎么就攤上這事瓶殃〕浒” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵遥椿,是天一觀的道長(zhǎng)基矮。 經(jīng)常有香客問(wèn)我,道長(zhǎng)冠场,這世上最難降的妖魔是什么家浇? 我笑而不...
    開封第一講書人閱讀 58,925評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮碴裙,結(jié)果婚禮上钢悲,老公的妹妹穿的比我還像新娘点额。我一直安慰自己,他們只是感情好莺琳,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評(píng)論 6 392
  • 文/花漫 我一把揭開白布还棱。 她就那樣靜靜地躺著,像睡著了一般芦昔。 火紅的嫁衣襯著肌膚如雪诱贿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評(píng)論 1 305
  • 那天咕缎,我揣著相機(jī)與錄音珠十,去河邊找鬼。 笑死凭豪,一個(gè)胖子當(dāng)著我的面吹牛焙蹭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嫂伞,決...
    沈念sama閱讀 40,447評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼孔厉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了帖努?” 一聲冷哼從身側(cè)響起撰豺,我...
    開封第一講書人閱讀 39,349評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拼余,沒(méi)想到半個(gè)月后污桦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡匙监,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評(píng)論 3 337
  • 正文 我和宋清朗相戀三年凡橱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亭姥。...
    茶點(diǎn)故事閱讀 40,127評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡稼钩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出达罗,到底是詐尸還是另有隱情坝撑,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評(píng)論 5 346
  • 正文 年R本政府宣布粮揉,位于F島的核電站巡李,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏滔蝉。R本人自食惡果不足惜击儡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蝠引。 院中可真熱鬧阳谍,春花似錦蛀柴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至训貌,卻和暖如春制肮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背递沪。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工豺鼻, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人款慨。 一個(gè)月前我還...
    沈念sama閱讀 48,388評(píng)論 3 373
  • 正文 我出身青樓士飒,卻偏偏與公主長(zhǎng)得像孤个,于是被迫代替她去往敵國(guó)和親窃祝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蔓挖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容