WeexSDK源碼分析(iOS)

0.從工做原理談起

 Weex 表面上是一個客戶端技術,但實際上它串聯起了從本地開發、雲端部署到分發的整個鏈路。開發者首先可在本地像編寫 web 頁面同樣編寫一個 app 的界面,而後經過命令行工具將之編譯成一段 JavaScript 代碼,生成一個 Weex 的 JS bundle;同時,開發者能夠將生成的 JS bundle 部署至雲端,而後經過網絡請求或預下發的方式加載至用戶的移動應用客戶端;在移動應用客戶端裏,Weex SDK 會準備好一個 JavaScript 執行環境,而且在用戶打開一個 Weex 頁面時在這個執行環境中執行相應的 JS bundle,並將執行過程當中產生的各類命令發送到 native 端進行界面渲染、數據存儲、網絡通訊、調用設備功能及用戶交互響應等功能;同時,若是用戶但願使用瀏覽器訪問這個界面,那麼他能夠在瀏覽器裏打開一個相同的 web 頁面,這個頁面和移動應用使用相同的頁面源代碼,但被編譯成適合Web展現的JS Bundle,經過瀏覽器裏的 JavaScript 引擎及 Weex SDK 運行起來的。html

上面是Weex官方的介紹。下面對Native端的原理作一下細分:webpack

下面咱們開始對Native端的解析,作一下分析。ios

1.WeexSDK初始化

咱們新建一個Weex項目以後,經過「weex platform add ios」,能夠添加iOS的工程代碼。打開該工程,能夠看到以下內容:web

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

該代碼通常在application: didFinishLaunchingWithOptions:中進行調用,用於初始化WeexSDK,下面咱們來看一下這部分的源碼實現,經過查看源碼,更深刻的理解WeexSDK。數組

2.WXAppConfiguration

@interface WXAppConfiguration : NSObject

/**
 * @abstract Group or organization of your app, default value is nil.
 */
+ (NSString *)appGroup;
+ (void)setAppGroup:(NSString *) appGroup;

/**
 * @abstract Name of your app, default is value for CFBundleDisplayName in main bundle.
 */
+ (NSString *)appName;
+ (void)setAppName:(NSString *)appName;

/**
 * @abstract Version of your app, default is value for CFBundleShortVersionString in main bundle.
 */
+ (NSString *)appVersion;
+ (void)setAppVersion:(NSString *)appVersion;

/**
 * @abstract External user agent of your app, all requests sent by weex will set the user agent on header,  default value is nil.
 */
+ (NSString *)externalUserAgent;
+ (void)setExternalUserAgent:(NSString *)userAgent;

/**
 * @abstract JSFrameworkVersion
 */
+ (NSString *)JSFrameworkVersion;
+ (void)setJSFrameworkVersion:(NSString *)JSFrameworkVersion;

/**
 + * @abstract JSFrameworkLibSize
 + */
+ (NSUInteger)JSFrameworkLibSize;
+ (void)setJSFrameworkLibSize:(NSUInteger)JSFrameworkLibSize;

/*
 *  @abstract customizeProtocolClasses
 */
+ (NSArray*)customizeProtocolClasses;
+ (void)setCustomizeProtocolClasses:(NSArray*)customizeProtocolClasses;

@end

從.h文件能夠看到,該類是用來用來記錄App配置信息的。全部方法都是類方法,內部實現是用單例實現的,.h用類方法是爲了方便調用。瀏覽器

3.實質初始化

[WXSDKEngine initSDKEnvironment];

該方法都作了哪些事情呢?緩存

+ (void)initSDKEnvironment
{
    // 加載本地的main.js
    NSString *filePath = [[NSBundle bundleForClass:self] pathForResource:@"native-bundle-main" ofType:@"js"];
    NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    // 初始化SDK環境
    [WXSDKEngine initSDKEnvironment:script];
    
    // 模擬器版本特殊代碼
#if TARGET_OS_SIMULATOR
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [WXSimulatorShortcutManager registerSimulatorShortcutWithKey:@"i" modifierFlags:UIKeyModifierCommand | UIKeyModifierAlternate action:^{
            NSURL *URL = [NSURL URLWithString:@"http://localhost:8687/launchDebugger"];
            NSURLRequest *request = [NSURLRequest requestWithURL:URL];
            
            NSURLSession *session = [NSURLSession sharedSession];
            NSURLSessionDataTask *task = [session dataTaskWithRequest:request
                                                    completionHandler:
                                          ^(NSData *data, NSURLResponse *response, NSError *error) {
                                              // ...
                                          }];
            
            [task resume];
            WXLogInfo(@"Launching browser...");
            
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self connectDebugServer:@"ws://localhost:8687/debugger/0/renderer"];
            });
            
        }];
    });
#endif
}

+ (void)initSDKEnvironment:(NSString *)script
{
    // 打點記錄狀態
    WX_MONITOR_PERF_START(WXPTInitalize)
    WX_MONITOR_PERF_START(WXPTInitalizeSync)
    
    if (!script || script.length <= 0) {
        NSMutableString *errMsg = [NSMutableString stringWithFormat:@"[WX_KEY_EXCEPTION_SDK_INIT_JSFM_INIT_FAILED] script don't exist:%@",script];
        [WXExceptionUtils commitCriticalExceptionRT:@"WX_KEY_EXCEPTION_SDK_INIT" errCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_SDK_INIT] function:@"initSDKEnvironment" exception:errMsg extParams:nil];
        WX_MONITOR_FAIL(WXMTJSFramework, WX_ERR_JSFRAMEWORK_LOAD, errMsg);
        return;
    }
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 註冊Components,Modules,Handlers
 [self registerDefaults]; // 執行JsFramework
 [[WXSDKManager bridgeMgr] executeJsFramework:script];
    });
    // 打點記錄狀態
    WX_MONITOR_PERF_END(WXPTInitalizeSync)
    
}

從源碼能夠看到,初始化總共作了四件事情:安全

  • WXMonitor監視器記錄狀態;
  • WXSDKEngine的初始化
  • 加載本地的main.js;
  • 模擬器WXSimulatorShortcutManager鏈接本地server。

3.1WXMonitor

WXMonitor是一個普通的對象,它裏面只存儲了一個線程安全的字典WXThreadSafeMutableDictionary。weex

@interface WXThreadSafeMutableDictionary<KeyType, ObjectType> : NSMutableDictionary

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

@end

WXMonitor在整個Weex裏面擔任的職責是記錄下各個操做的tag值和記錄成功和失敗的緣由網絡

在WXSDKInstance初始化以前,全部的全局的global操做都會放在WXMonitor的WXThreadSafeMutableDictionary中。當WXSDKInstance初始化以後,即WXPerformanceTag中instance如下的全部操做都會放在WXSDKInstance的performanceDict中。

3.2加載本地的main.js

main.js是通過webpack壓縮以後的文件,它是Native這邊的JS Framework。

3.3WXSDKEngine的初始化

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

從上面能夠看到,WXSDKEngine在初始化的時候,分別註冊了Components、Modules、Handlers。這是 Weex 與 Native 應用層交互最核心的部分,能夠理解爲「組件」。其中 Component 是爲了映射 Html 的一些標籤,Module 中是提供一些 Native 的方法供 Weex 調用,Handler 是一些協議的實現。

註冊完 Weex 默認的「組件」 以後,注入3.2小節的那段 JS,這個時候 Vue 的標籤和動做才能被 Weex 所識別和轉換。

3.4執行JsFramework

[[WXSDKManager bridgeMgr] executeJsFramework:script];

WXSDKManager會調用WXBridgeManager去執行SDK裏面的main.js文件。

- (void)executeJsFramework:(NSString *)script
{
    if (!script) return;
    
    __weak typeof(self) weakSelf = self;
    WXPerformBlockOnBridgeThread(^(){
        [weakSelf.bridgeCtx executeJsFramework:script];
    });
}

WXBridgeManager經過WXBridgeContext調用executeJsFramework:方法,這裏的方法調用也是在子線程中進行的。

- (void)executeJsFramework:(NSString *)script
{
    WXAssertBridgeThread();
    WXAssertParam(script);
    
    WX_MONITOR_PERF_START(WXPTFrameworkExecute);

    [self.jsBridge executeJSFramework:script];
    
    WX_MONITOR_PERF_END(WXPTFrameworkExecute);
    
    if ([self.jsBridge exception]) {
        NSString *exception = [[self.jsBridge exception] toString];
        NSMutableString *errMsg = [NSMutableString stringWithFormat:@"[WX_KEY_EXCEPTION_SDK_INIT_JSFM_INIT_FAILED] %@",exception];
        [WXExceptionUtils commitCriticalExceptionRT:@"WX_KEY_EXCEPTION_SDK_INIT" errCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_SDK_INIT] function:@"" exception:errMsg extParams:nil];
        WX_MONITOR_FAIL(WXMTJSFramework, WX_ERR_JSFRAMEWORK_EXECUTE, errMsg);
    } else {
        WX_MONITOR_SUCCESS(WXMTJSFramework);
        //the JSFramework has been load successfully.
        // 至此JSFramework是徹底加載完成了
        self.frameworkLoadFinished = YES;
        // 執行全部註冊的JsService
 [self executeAllJsService]; // 獲取JSFramework版本號
        JSValue *frameworkVersion = [self.jsBridge callJSMethod:@"getJSFMVersion" args:nil];
        if (frameworkVersion && [frameworkVersion isString]) {
            // 把版本號存入WXAppConfiguration中
            [WXAppConfiguration setJSFrameworkVersion:[frameworkVersion toString]];
        }
        
        if (script) {
             [WXAppConfiguration setJSFrameworkLibSize:[script lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];
        }
        
        //execute methods which has been stored in methodQueue temporarily.
        // 執行以前緩存在_methodQueue數組裏面的全部方法
        for (NSDictionary *method in _methodQueue) {
            [self callJSMethod:method[@"method"] args:method[@"args"]];
        }
        
        [_methodQueue removeAllObjects];
        // 至此,初始化工做完成
        WX_MONITOR_PERF_END(WXPTInitalize);
    };
}

WX_MONITOR_PERF_START是在操做以前標記WXPTFrameworkExecute,執行完JSFramework之後,用WX_MONITOR_PERF_END標記執行完成。

- (void)executeJSFramework:(NSString *)frameworkScript
{
    WXAssertParam(frameworkScript);
    if (WX_SYS_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
        [_jsContext evaluateScript:frameworkScript withSourceURL:[NSURL URLWithString:@"native-bundle-main.js"]];
    }else{
        [_jsContext evaluateScript:frameworkScript];
    }
}
加載JSFramework的核心代碼在這裏,經過JSContext執行evaluateScript:來加載JSFramework。因爲這裏並無返回值,因此加載的JSFramework的目的僅僅是聲明瞭裏面的全部方法,並無調用。這也符合OC加載其餘Framework的過程,加載只是加載到內存中,Framework裏面的方法能夠隨時被調用,而不是一加載就調用其全部的方法。
加載完成JSFramework之後,就要開始加載以前緩存的JSService和JSMethod。JSService是在jsServiceQueue中緩存的( 默認SDK裏面,是沒有的)。JSMethod( 組件、模塊中緩存的)是在methodQueue中緩存的。
- (void)executeAllJsService
{
    for(NSDictionary *service in _jsServiceQueue) {
        NSString *script = [service valueForKey:@"script"];
        NSString *name = [service valueForKey:@"name"];
        [self executeJsService:script withName:name];
    }
    
    [_jsServiceQueue removeAllObjects];
}

for (NSDictionary *method in _methodQueue) {
            [self callJSMethod:method[@"method"] args:method[@"args"]];
        }

- (JSValue *)callJSMethod:(NSString *)method args:(NSArray *)args
{
    WXLogDebug(@"Calling JS... method:%@, args:%@", method, args);
    return [[_jsContext globalObject] invokeMethod:method withArguments:args];
}
因爲_methodQueue裏面裝的都是全局的js方法,因此須要調用invokeMethod: withArguments:去執行。當這一切都加載完成,SDK的初始化工做就基本完成了,這裏就會標記上WXPTInitalize結束。

這裏再補充討論一下,jsBridge第一次是如何被加載進來的?

- (id<WXBridgeProtocol>)jsBridge
{
    WXAssertBridgeThread();
    _debugJS = [WXDebugTool isDevToolDebug];
    
    Class bridgeClass = _debugJS ? NSClassFromString(@"WXDebugger") : [WXJSCoreBridge class];
    
    if (_jsBridge && [_jsBridge isKindOfClass:bridgeClass]) {
        return _jsBridge;
    }
    
    if (_jsBridge) {
        [_methodQueue removeAllObjects];
        _frameworkLoadFinished = NO;
    }
    
    _jsBridge = _debugJS ? [NSClassFromString(@"WXDebugger") alloc] : [[WXJSCoreBridge alloc] init];
    
    [self registerGlobalFunctions];
    
    return _jsBridge;
}

第一次進入這個函數沒有jsBridge實例的時候,會先生成WXJSCoreBridge的實例,而後緊接着註冊全局的函數。等第二次再調用這個函數的時候,_jsBridge已是WXJSCoreBridge類型了,就會直接return,下面的語句也不會再重複執行了。

typedef NSInteger(^WXJSCallNative)(NSString *instance, NSArray *tasks, NSString *callback);
typedef NSInteger(^WXJSCallAddElement)(NSString *instanceId,  NSString *parentRef, NSDictionary *elementData, NSInteger index);
typedef NSInteger(^WXJSCallCreateBody)(NSString *instanceId, NSDictionary *bodyData);
typedef NSInteger(^WXJSCallRemoveElement)(NSString *instanceId,NSString *ref);
typedef NSInteger(^WXJSCallMoveElement)(NSString *instanceId,NSString *ref,NSString *parentRef,NSInteger index);
typedef NSInteger(^WXJSCallUpdateAttrs)(NSString *instanceId,NSString *ref,NSDictionary *attrsData);
typedef NSInteger(^WXJSCallUpdateStyle)(NSString *instanceId,NSString *ref,NSDictionary *stylesData);
typedef NSInteger(^WXJSCallAddEvent)(NSString *instanceId,NSString *ref,NSString *event);
typedef NSInteger(^WXJSCallRemoveEvent)(NSString *instanceId,NSString *ref,NSString *event);
typedef NSInteger(^WXJSCallCreateFinish)(NSString *instanceId);
typedef NSInvocation *(^WXJSCallNativeModule)(NSString *instanceId, NSString *moduleName, NSString *methodName, NSArray *args, NSDictionary *options);
typedef void (^WXJSCallNativeComponent)(NSString *instanceId, NSString *componentRef, NSString *methodName, NSArray *args, NSDictionary *options);

上面的閉包,是OC封裝暴露給JS的。對應的全局函數:

- (void)registerCallNative:(WXJSCallNative)callNative
{
    JSValue* (^callNativeBlock)(JSValue *, JSValue *, JSValue *) = ^JSValue*(JSValue *instance, JSValue *tasks, JSValue *callback){
        NSString *instanceId = [instance toString];
        NSArray *tasksArray = [tasks toArray];
        NSString *callbackId = [callback toString];
        WXLogDebug(@"Calling native... instance:%@, tasks:%@, callback:%@", instanceId, tasksArray, callbackId);
        return [JSValue valueWithInt32:(int32_t)callNative(instanceId, tasksArray, callbackId) inContext:[JSContext currentContext]];
    };
    
    _jsContext[@"callNative"] = callNativeBlock;
}

- (void)registerCallAddElement:(WXJSCallAddElement)callAddElement
{
    id callAddElementBlock = ^(JSValue *instanceId, JSValue *ref, JSValue *element, JSValue *index, JSValue *ifCallback) {
        
        NSString *instanceIdString = [instanceId toString];
        NSDictionary *componentData = [element toDictionary];
        NSString *parentRef = [ref toString];
        NSInteger insertIndex = [[index toNumber] integerValue];
        [WXTracingManager startTracingWithInstanceId:instanceIdString ref:componentData[@"ref"] className:nil name:WXTJSCall phase:WXTracingBegin functionName:@"addElement" options:@{@"threadName":WXTJSBridgeThread,@"componentData":componentData}];
         WXLogDebug(@"callAddElement...%@, %@, %@, %ld", instanceIdString, parentRef, componentData, (long)insertIndex);
        
        return [JSValue valueWithInt32:(int32_t)callAddElement(instanceIdString, parentRef, componentData, insertIndex) inContext:[JSContext currentContext]];
    };

    _jsContext[@"callAddElement"] = callAddElementBlock;
}

- (void)registerCallNativeModule:(WXJSCallNativeModule)callNativeModuleBlock
{
    _jsContext[@"callNativeModule"] = ^JSValue *(JSValue *instanceId, JSValue *moduleName, JSValue *methodName, JSValue *args, JSValue *options) {
        NSString *instanceIdString = [instanceId toString];
        NSString *moduleNameString = [moduleName toString];
        NSString *methodNameString = [methodName toString];
        NSArray *argsArray = [args toArray];
        NSDictionary *optionsDic = [options toDictionary];
        
        WXLogDebug(@"callNativeModule...%@,%@,%@,%@", instanceIdString, moduleNameString, methodNameString, argsArray);
        
        NSInvocation *invocation = callNativeModuleBlock(instanceIdString, moduleNameString, methodNameString, argsArray, optionsDic);
        JSValue *returnValue = [JSValue wx_valueWithReturnValueFromInvocation:invocation inContext:[JSContext currentContext]];
        [WXTracingManager startTracingWithInstanceId:instanceIdString ref:nil className:nil name:moduleNameString phase:WXTracingInstant functionName:methodNameString options:nil];
        return returnValue;
    };
}

- (void)registerCallNativeComponent:(WXJSCallNativeComponent)callNativeComponentBlock
{
    _jsContext[@"callNativeComponent"] = ^void(JSValue *instanceId, JSValue *componentName, JSValue *methodName, JSValue *args, JSValue *options) {
        NSString *instanceIdString = [instanceId toString];
        NSString *componentNameString = [componentName toString];
        NSString *methodNameString = [methodName toString];
        NSArray *argsArray = [args toArray];
        NSDictionary *optionsDic = [options toDictionary];
        
        WXLogDebug(@"callNativeComponent...%@,%@,%@,%@", instanceIdString, componentNameString, methodNameString, argsArray);
        
        callNativeComponentBlock(instanceIdString, componentNameString, methodNameString, argsArray, optionsDic);
    };
}

因爲JS的方法的寫法,多個參數是依次寫在小括號裏面的,和OC多個參數中間用:號隔開是不同的,全部在暴露給JS的時候,須要把Block再包裝一層。包裝的4個方法如上,最後把這4個方法注入到JSContext中。

如上圖,灰色的就是OC本地傳入的Block,外面在包一層,變成JS的方法,注入到JSContext中。

相關文章
相關標籤/搜索