zoukankan      html  css  js  c++  java
  • 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 运行起来的。

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

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

    1.WeexSDK初始化

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

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

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

    2.WXAppConfiguration

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

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

    3.实质初始化

    [WXSDKEngine initSDKEnvironment];

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

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

    @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中。

  • 相关阅读:
    Win7 IE11无法打开的可能解决办法
    s​q​l​ ​s​e​r​v​e​r​ ​2​0​0​0​登​录​名​与​数​据​库​用​户​名​的​关​联​问​题
    错误 0xc0202049: 数据流任务 1: 无法在只读列“ID”中插入数据
    清空SQL Server数据库中所有表数据的方法
    01-鼠标点击空白处实现层隐藏
    01-artDialog4.1.7常用整理
    ASP.NET MVC HtmlHelper用法大全
    随机生成十个数 填充数组
    字串加密、解密
    动手动脑、String类函数的使用说明
  • 原文地址:https://www.cnblogs.com/LeeGof/p/9016570.html
Copyright © 2011-2022 走看看