zoukankan      html  css  js  c++  java
  • Weex是如何让JS调用产生原生UIView的?

    从官方的Demo,我们知道,要在客户端显示Weex页面,是通过WXSDKInstance的实例实现的。我们先来看看这个类里面都有什么:

    @interface WXSDKInstance : NSObject
    
    // 需要渲染的viewController
    @property (nonatomic, weak) UIViewController *viewController;
    
    // Native根容器的View是完全受WXSDKInstance控制,开发者无法更改
    @property (nonatomic, strong) UIView *rootView;
    
    // 如果组件想固定rootview的frame,可以把这个属性设置为YES,当weex进行layout的时候,就不会改变rootview的frame了。反之设置为NO
    @property (nonatomic, assign) BOOL isRootViewFrozen;
    
    /**
     * Which indicates current instance needs to be validated or not to load,default value is false.
     **/
    @property (nonatomic, assign) BOOL needValidate;
    
    // weex bundle的scriptURL
    @property (nonatomic, strong) NSURL *scriptURL;
    
    // 父Instance
    @property (nonatomic, weak) WXSDKInstance *parentInstance;
    
    // 父Instance节点的引用
    @property (nonatomic, weak) NSString *parentNodeRef;
    
    // 用来标识当前weex instance独一无二的ID
    @property (nonatomic, strong) NSString *instanceId;
    
    /**
     * Which indicates current instance needs to be prerender or not,default value is false.
     **/
    @property (nonatomic, assign) BOOL needPrerender;
    
    // 当前weex instance的状态
    @property (nonatomic, assign) WXState state;
    
    // 当weex instance完成rootView的创建时的回调block
    @property (nonatomic, copy) void (^onCreate)(UIView *);
    
    // 根容器的frame改变时候的回调
    @property (nonatomic, copy) void (^onLayoutChange)(UIView *);
    
    // 当weex instance完成渲染时的回调block
    @property (nonatomic, copy) void (^renderFinish)(UIView *);
    
    // 当weex instance刷新完成时的回调block
    @property (nonatomic, copy) void (^refreshFinish)(UIView *);
    
    // 当weex instance渲染失败时的回调block
    @property (nonatomic, copy) void (^onFailed)(NSError *error);
    
    /**
     *  The callback triggered when js occurs runtime error while executing.
     *
     *  @return A block that takes a WXJSExceptionInfo argument, which is the exception info
     **/
    @property (nonatomic, copy) void (^onJSRuntimeException)(WXJSExceptionInfo * jsException);
    
    // 当weex instance页面滚动时的回调block
    @property (nonatomic, copy) void (^onScroll)(CGPoint contentOffset);
    
    // 当weex instance渲染过程中的回调block
    @property (nonatomic, copy) void (^onRenderProgress)(CGRect renderRect);
    
    /**
     * The callback triggered when the bundleJS request finished in the renderWithURL.
     * @return A block that takes response which the server response,request which send to server,data which the server returned and an error
     */
    @property (nonatomic, copy) void(^onJSDownloadedFinish)(WXResourceResponse *response,WXResourceRequest *request,NSData *data, NSError* error);
    
    // 当前weex instance的frame
    @property (nonatomic, assign) CGRect frame;
    
    // user存储的一些信息
    @property (atomic, strong) NSMutableDictionary *userInfo;
    
    // css单元和设备像素的换算比例因子
    @property (nonatomic, assign, readonly) CGFloat pixelScaleFactor;
    
    // 是否监测组件的渲染
    @property (nonatomic, assign)BOOL trackComponent;
    /**
     * Renders weex view with bundle url.
     *
     * @param url The url of bundle rendered to a weex view.
     **/
    - (void)renderWithURL:(NSURL *)url;
    
    /**
     * Renders weex view with bundle url and some others.
     *
     * @param url The url of bundle rendered to a weex view.
     *
     * @param options The params passed by user
     *
     * @param data The data the bundle needs when rendered.  Defalut is nil.
     **/
    - (void)renderWithURL:(NSURL *)url options:(NSDictionary *)options data:(id)data;
    
    ///**
    // * Renders weex view with resource request.
    // *
    // * @param request The resource request specifying the URL to render with.
    // *
    // * @param options The params passed by user.
    // *
    // * @param data The data the bundle needs when rendered.  Defalut is nil.
    // **/
    //- (void)renderWithRequest:(WXResourceRequest *)request options:(NSDictionary *)options data:(id)data;
    
    /**
     * Renders weex view with source string of bundle and some others.
     *
     * @param options The params passed by user.
     *
     * @param data The data the bundle needs when rendered. Defalut is nil.
     **/
    - (void)renderView:(NSString *)source options:(NSDictionary *)options data:(id)data;
    
    // forcedReload为YES,每次加载都会从URL重新读取,为NO,会从缓存中读取
    - (void)reload:(BOOL)forcedReload;
    
    /**
     * Refreshes current instance with data.
     *
     * @param data The data the bundle needs when rendered.
     **/
    - (void)refreshInstance:(id)data;
    
    /**
     * Destroys current instance.
     **/
    - (void)destroyInstance;
    
    /**
     * Trigger full GC, for dev and debug only.
     **/
    - (void)forceGarbageCollection;
    
    /**
     * get module instance by class
     */
    - (id)moduleForClass:(Class)moduleClass;
    
    /**
     * get Component instance by ref, must be called on component thread by calling WXPerformBlockOnComponentThread
     */
    - (WXComponent *)componentForRef:(NSString *)ref;
    
    /**
     * Number of components created, must be called on component thread by calling WXPerformBlockOnComponentThread
     */
    - (NSUInteger)numberOfComponents;
    
    
    /**
     * check whether the module eventName is registered
     */
    - (BOOL)checkModuleEventRegistered:(NSString*)event moduleClassName:(NSString*)moduleClassName;
    
    /**
     * fire module event;
     * @param module which module you fire event to
     * @param eventName the event name
     * @param params event params
     */
    - (void)fireModuleEvent:(Class)module eventName:(NSString *)eventName params:(NSDictionary*)params;
    
    /**
     * fire global event
     */
    - (void)fireGlobalEvent:(NSString *)eventName params:(NSDictionary *)params;
    
    /**
     * complete url based with bundle url
     */
    - (NSURL *)completeURL:(NSString *)url;
    
    /**
     * application performance statistics
     */
    @property (nonatomic, strong) NSString *bizType;
    @property (nonatomic, strong) NSString *pageName;
    @property (nonatomic, weak) id pageObject;
    @property (nonatomic, strong) NSMutableDictionary *performanceDict;
    
    
    /** 
     * Deprecated 
     */
    @property (nonatomic, strong) NSDictionary *properties DEPRECATED_MSG_ATTRIBUTE();
    @property (nonatomic, assign) NSTimeInterval networkTime DEPRECATED_MSG_ATTRIBUTE();
    @property (nonatomic, copy) void (^updateFinish)(UIView *);
    
    @end
    View Code

    一个WXSDKInstance就对应一个UIViewController,所以每个Weex的页面都有一个与之对应的WXSDKInstance:

    @property (nonatomic, strong) WXSDKInstance *instance;

    WXSDKInstance一般通过调用renderWithURL方法来渲染页面。

    - (void)p_render {
        [_instance destroyInstance];
        _instance = [[WXSDKInstance alloc] init];
        _instance.viewController = self;
        _instance.frame = (CGRect){CGPointZero, kScreenWidth, kScreenHeight};
        
        __weak typeof(self) weakSelf = self;
        _instance.onCreate = ^(UIView *view) {
            [weakSelf.weexView removeFromSuperview];
            weakSelf.weexView = view;
            [weakSelf.view addSubview:weakSelf.weexView];
        };
        
        _instance.onFailed = ^(NSError *error) {
            //process failure
        };
        
        _instance.renderFinish = ^ (UIView *view) {
            //process renderFinish
        };
        
        if (!self.url) {
            WXLogError(@"error: render url is nil");
            return;
        }
        [_instance renderWithURL:self.url options:@{@"bundleUrl":[self.url absoluteString]} data:nil];
    }

    由于WXSDKInstance是支持实时刷新,所以在创建的时候需要先销毁掉原来的,再创建一个新的。

    WXSDKInstance支持设置各种状态时候的回调callback函数,具体支持哪些状态,可以看WXSDKInstance的定义。

    Weex支持从本地加载JS,也支持从服务器加载JS。如果从本地加载,那么可以用下面的方法,从本地加载一个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];
    }

    最后渲染页面就是通过调用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;
        }
        
        self.needValidate = [[WXHandlerFactory handlerForProtocol:@protocol(WXValidateProtocol)] needValidate:url];
        
        WXResourceRequest *request = [WXResourceRequest requestWithURL:url resourceType:WXResourceTypeMainBundle referrer:@"" cachePolicy:NSURLRequestUseProtocolCachePolicy];
        [self _renderWithRequest:request options:options data:data];
        [WXTracingManager startTracingWithInstanceId:self.instanceId ref:nil className:nil name:WXTNetworkHanding phase:WXTracingBegin functionName:@"renderWithURL" options:@{@"bundleUrl":url?[url absoluteString]:@"",@"threadName":WXTMainThread}];
    }

    在WXSDKInstance调用renderWithURL:options:data:方法的时候,会生成一个WXResourceRequest。

    @interface WXResourceRequest : NSMutableURLRequest
    
    @property (nonatomic, strong) id taskIdentifier;
    @property (nonatomic, assign) WXResourceType type;
    
    @property (nonatomic, strong) NSString *referrer;
    @property (nonatomic, strong) NSString *userAgent;
    
    + (instancetype)requestWithURL:(NSURL *)url
                      resourceType:(WXResourceType)type
                          referrer:(NSString *)referrer
                       cachePolicy:(NSURLRequestCachePolicy)cachePolicy;
    
    @end

    WXResourceRequest其实也就是对NSMutableURLRequest的一层封装。

    下面来分析一下最核心的函数renderWithURL:options:data:(以下的代码实现在源码的基础上略有删减,源码太长,删减以后并不影响阅读)

    - (void)_renderWithRequest:(WXResourceRequest *)request options:(NSDictionary *)options data:(id)data;
    {
        NSURL *url = request.URL;
        _scriptURL = url;
        _jsData = data;
        NSMutableDictionary *newOptions = [options mutableCopy] ?: [NSMutableDictionary new];
        
        if (!newOptions[bundleUrlOptionKey]) {
            newOptions[bundleUrlOptionKey] = url.absoluteString;
        }
        // compatible with some wrong type, remove this hopefully in the future.
        if ([newOptions[bundleUrlOptionKey] isKindOfClass:[NSURL class]]) {
            WXLogWarning(@"Error type in options with key:bundleUrl, should be of type NSString, not NSURL!");
            newOptions[bundleUrlOptionKey] = ((NSURL*)newOptions[bundleUrlOptionKey]).absoluteString;
        }
        _options = [newOptions copy];
      
        if (!self.pageName || [self.pageName isEqualToString:@""]) {
            self.pageName = url.absoluteString ? : @"";
        }
        
        request.userAgent = [WXUtility userAgent];
        
        WX_MONITOR_INSTANCE_PERF_START(WXPTJSDownload, self);
        __weak typeof(self) weakSelf = self;
        _mainBundleLoader = [[WXResourceLoader alloc] initWithRequest:request];;
        // 请求完成的回调
        _mainBundleLoader.onFinished = ^(WXResourceResponse *response, NSData *data) {
            __strong typeof(weakSelf) strongSelf = weakSelf;
            NSError *error = nil;
            if ([response isKindOfClass:[NSHTTPURLResponse class]] && ((NSHTTPURLResponse *)response).statusCode != 200) {
                error = [NSError errorWithDomain:WX_ERROR_DOMAIN
                                            code:((NSHTTPURLResponse *)response).statusCode
                                        userInfo:@{@"message":@"status code error."}];
                if (strongSelf.onFailed) {
                    strongSelf.onFailed(error);
                }
            }
            
            if (strongSelf.onJSDownloadedFinish) {
                strongSelf.onJSDownloadedFinish(response, request, data, error);
            }
            
            if (error) {
                WXJSExceptionInfo * jsExceptionInfo = [[WXJSExceptionInfo alloc] initWithInstanceId:@"" bundleUrl:[request.URL absoluteString] errorCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_JS_DOWNLOAD] functionName:@"_renderWithRequest:options:data:" exception:[error localizedDescription]  userInfo:nil];
                [WXExceptionUtils commitCriticalExceptionRT:jsExceptionInfo];
                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);
    
                WXJSExceptionInfo * jsExceptionInfo = [[WXJSExceptionInfo alloc] initWithInstanceId:@"" bundleUrl:[request.URL absoluteString] errorCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_JS_DOWNLOAD] functionName:@"_renderWithRequest:options:data:" exception:@"no data return"  userInfo:nil];
                [WXExceptionUtils commitCriticalExceptionRT:jsExceptionInfo];
                
                if (strongSelf.onFailed) {
                    strongSelf.onFailed(error);
                }
                return;
            }
            
            NSString *jsBundleString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            if (!jsBundleString) {
                WX_MONITOR_FAIL_ON_PAGE(WXMTJSDownload, WX_ERR_JSBUNDLE_STRING_CONVERT, @"data converting to string failed.", strongSelf.pageName)
                return;
            }
            if (!strongSelf.userInfo) {
                strongSelf.userInfo = [NSMutableDictionary new];
            }
            strongSelf.userInfo[@"jsMainBundleStringContentLength"] = @([jsBundleString length]);
            strongSelf.userInfo[@"jsMainBundleStringContentMd5"] = [WXUtility md5:jsBundleString];
    
            WX_MONITOR_SUCCESS_ON_PAGE(WXMTJSDownload, strongSelf.pageName);
            WX_MONITOR_INSTANCE_PERF_END(WXPTJSDownload, strongSelf);
            
            [strongSelf _renderWithMainBundleString:jsBundleString];
            [WXTracingManager setBundleJSType:jsBundleString instanceId:weakSelf.instanceId];
        };
        // 请求失败的回调
        _mainBundleLoader.onFailed = ^(NSError *loadError) {
            NSString *errorMessage = [NSString stringWithFormat:@"Request to %@ occurs an error:%@", request.URL, loadError.localizedDescription];
            
            WX_MONITOR_FAIL_ON_PAGE(WXMTJSDownload, [loadError.domain isEqualToString:NSURLErrorDomain] && loadError.code == NSURLErrorNotConnectedToInternet ? WX_ERR_NOT_CONNECTED_TO_INTERNET : WX_ERR_JSBUNDLE_DOWNLOAD, errorMessage, weakSelf.pageName);
            
            if (weakSelf.onFailed) {
                weakSelf.onFailed(error);
            }
        };
        
        [_mainBundleLoader start];
    }

    归结起来干了2件事情,第一步,生成了WXResourceLoader,并设置了它的onFinished和onFailed回调。第二步调用了start方法。

    在WXSDKInstance中强持有了一个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里面含有一个WXResourceRequest,所以WXResourceRequest也可以看出对网络请求的封装,并且提供了5种不同状态的callback回调函数。

    - (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!");
        }
    }

    在调用了WXResourceLoader的start方法以后,会先判断是不是本地的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 alloc]initWithURL:url statusCode:200 HTTPVersion:@"1.1" headerFields:nil], fileData);
            }
        });
    }

    本地文件就直接回调onFinished函数。

    如果不是本地的文件,就开始发起网络请求,请求服务器端的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];
    }

    这里的网络请求就是普通的正常NSURLSession网络请求。如果成功,最终都会执行onFinished的回调函数。

        _mainBundleLoader.onFinished = ^(WXResourceResponse *response, NSData *data) {
            __strong typeof(weakSelf) strongSelf = weakSelf;
            NSError *error = nil;
            if ([response isKindOfClass:[NSHTTPURLResponse class]] && ((NSHTTPURLResponse *)response).statusCode != 200) {
                error = [NSError errorWithDomain:WX_ERROR_DOMAIN
                                            code:((NSHTTPURLResponse *)response).statusCode
                                        userInfo:@{@"message":@"status code error."}];
                if (strongSelf.onFailed) {
                    strongSelf.onFailed(error);
                }
            }
            
            if (strongSelf.onJSDownloadedFinish) {
                strongSelf.onJSDownloadedFinish(response, request, data, error);
            }
            
            if (error) {
                WXJSExceptionInfo * jsExceptionInfo = [[WXJSExceptionInfo alloc] initWithInstanceId:@"" bundleUrl:[request.URL absoluteString] errorCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_JS_DOWNLOAD] functionName:@"_renderWithRequest:options:data:" exception:[error localizedDescription]  userInfo:nil];
                [WXExceptionUtils commitCriticalExceptionRT:jsExceptionInfo];
                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);
    
                WXJSExceptionInfo * jsExceptionInfo = [[WXJSExceptionInfo alloc] initWithInstanceId:@"" bundleUrl:[request.URL absoluteString] errorCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_JS_DOWNLOAD] functionName:@"_renderWithRequest:options:data:" exception:@"no data return"  userInfo:nil];
                [WXExceptionUtils commitCriticalExceptionRT:jsExceptionInfo];
                
                if (strongSelf.onFailed) {
                    strongSelf.onFailed(error);
                }
                return;
            }
            
            NSString *jsBundleString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"下载下来的 jsBundleString = %@",jsBundleString);
            if (!jsBundleString) {
                WX_MONITOR_FAIL_ON_PAGE(WXMTJSDownload, WX_ERR_JSBUNDLE_STRING_CONVERT, @"data converting to string failed.", strongSelf.pageName)
                return;
            }
            if (!strongSelf.userInfo) {
                strongSelf.userInfo = [NSMutableDictionary new];
            }
            strongSelf.userInfo[@"jsMainBundleStringContentLength"] = @([jsBundleString length]);
            strongSelf.userInfo[@"jsMainBundleStringContentMd5"] = [WXUtility md5:jsBundleString];
    
            WX_MONITOR_SUCCESS_ON_PAGE(WXMTJSDownload, strongSelf.pageName);
            WX_MONITOR_INSTANCE_PERF_END(WXPTJSDownload, strongSelf);
            
            [strongSelf _renderWithMainBundleString:jsBundleString];
            [WXTracingManager setBundleJSType:jsBundleString instanceId:weakSelf.instanceId];
        };

    在onFinished的回调中,有几种错误判断,例如status code error、no data return、data converting to string failed等。

    如果一切正常,那么在onFinished的回调中其实就是拿到jsBundleString,并执行渲染操作。

    - (void)_renderWithMainBundleString:(NSString *)mainBundleString
    {
        if (!self.instanceId) {
            WXLogError(@"Fail to find instance!");
            return;
        }
        
        if (![WXUtility isBlankString:self.pageName]) {
            WXLog(@"Start rendering page:%@", self.pageName);
        } else {
            WXLogWarning(@"WXSDKInstance's pageName should be specified.");
            id<WXJSExceptionProtocol> jsExceptionHandler = [WXHandlerFactory handlerForProtocol:@protocol(WXJSExceptionProtocol)];
            if ([jsExceptionHandler respondsToSelector:@selector(onRuntimeCheckException:)]) {
                WXRuntimeCheckException * runtimeCheckException = [WXRuntimeCheckException new];
                runtimeCheckException.exception = @"We highly recommend you to set pageName.
     Using WXSDKInstance * instance = [WXSDKInstance new]; instance.pageName = @"your page name" to fix it";
                [jsExceptionHandler onRuntimeCheckException:runtimeCheckException];
            }
        }
        
        WX_MONITOR_INSTANCE_PERF_START(WXPTFirstScreenRender, self);
        WX_MONITOR_INSTANCE_PERF_START(WXPTAllRender, self);
        
        NSMutableDictionary *dictionary = [_options mutableCopy];
        if ([WXLog logLevel] >= WXLogLevelLog) {
            dictionary[@"debug"] = @(YES);
        }
        
        if ([WXDebugTool getReplacedBundleJS]) {
            mainBundleString = [WXDebugTool getReplacedBundleJS];
        }
        
        //TODO WXRootView
        //生成WXRootView
        WXPerformBlockOnMainThread(^{
            _rootView = [[WXRootView alloc] initWithFrame:self.frame];
            _rootView.instance = self;
            if(self.onCreate) {
                self.onCreate(_rootView);
            }
        });
        // ensure default modules/components/handlers are ready before create instance
        // 再次注册默认的模块modules、组件components、handlers,以确保在创建instance之前它们都被注册了
        [WXSDKEngine registerDefaults];
         [[NSNotificationCenter defaultCenter] postNotificationName:WX_SDKINSTANCE_WILL_RENDER object:self];
        
        [self _handleConfigCenter];
        _needDestroy = YES;
        [WXTracingManager startTracingWithInstanceId:self.instanceId ref:nil className:nil name:WXTExecJS phase:WXTracingBegin functionName:@"renderWithMainBundleString" options:@{@"threadName":WXTMainThread}];
        // 开始createInstance
        [[WXSDKManager bridgeMgr] createInstance:self.instanceId template:mainBundleString options:dictionary data:_jsData];
        [WXTracingManager startTracingWithInstanceId:self.instanceId ref:nil className:nil name:WXTExecJS phase:WXTracingEnd functionName:@"renderWithMainBundleString" options:@{@"threadName":WXTMainThread}];
        
        WX_MONITOR_PERF_SET(WXPTBundleSize, [mainBundleString lengthOfBytesUsingEncoding:NSUTF8StringEncoding], self);
    }

    这里WXSDKEngine还会重新再次注册一遍模块modules、组件components、handlers,以确保在创建instance之前它们都被注册了。

    - (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(^(){
            [WXTracingManager startTracingWithInstanceId:instance ref:nil className:nil name:WXTExecJS phase:WXTracingBegin functionName:@"createInstance" options:@{@"threadName":WXTJSBridgeThread}];
            [weakSelf.bridgeCtx createInstance:instance
                                      template:temp
                                       options:options
                                          data:data];
            [WXTracingManager startTracingWithInstanceId:instance ref:nil className:nil name:WXTExecJS phase:WXTracingEnd functionName:@"createInstance" options:@{@"threadName":WXTJSBridgeThread}];
            
        });
    }

    然后调用WXBridgeContext的createInstance:template:options:data:方法:

    - (void)createInstance:(NSString *)instance
                  template:(NSString *)temp
                   options:(NSDictionary *)options
                      data:(id)data
    {
        WXAssertBridgeThread();
        WXAssertParam(instance);
        
        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 ?: @{}];
        }
        WX_MONITOR_INSTANCE_PERF_START(WXFirstScreenJSFExecuteTime, [WXSDKManager instanceForID:instance]);
        WX_MONITOR_INSTANCE_PERF_START(WXPTJSCreateInstance, [WXSDKManager instanceForID:instance]);
        [self callJSMethod:@"createInstance" args:args];
        WX_MONITOR_INSTANCE_PERF_END(WXPTJSCreateInstance, [WXSDKManager instanceForID:instance]);
    }

    最终还是WXJSCoreBridge里面的JSContext调用:

    - (JSValue *)callJSMethod:(NSString *)method args:(NSArray *)args
    {
        WXLogDebug(@"Calling JS... method:%@, args:%@", method, args);
        return [[_jsContext globalObject] invokeMethod:method withArguments:args];
    }

    调用JS的"createInstance"方法。从此处开始,就开始和JSFramework进行相互调用了。下面我们看看整个过程的流程图:

    下面我们通过一个示例来说明一下上面的过程。 首先我们在Weex中编写如下代码:

    <template>
        <div class="container">
            <image src="https://gw.alicdn.com/tfs/TB1yopEdgoQMeJjy1XaXXcSsFXa-640-302.png" class="pic" @click="picClick"></image>
            <text class="text">{{title}}</text>
        </div>
    </template>
    
    <style>
    .container {
      align-items: center;
    }
    .pic {
      margin-top: 100px;
      width: 424px;
      height: 200px;
    }
    .text {
      margin-top: 40px;
      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 = '图片被点击'
          } else {
            this.title = 'Hello Weex'
          }
        }
      }
    }
    </script>

    运行效果大致如下:

    上面的vue文件,经过Weex编译以后,就变成了index.js,里面的代码如下:

    // { "framework": "Vue"} 
    
    /******/ (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] = {
    /******/             i: moduleId,
    /******/             l: false,
    /******/             exports: {}
    /******/         };
    /******/
    /******/         // Execute the module function
    /******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
    /******/
    /******/         // Flag the module as loaded
    /******/         module.l = 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;
    /******/
    /******/     // define getter function for harmony exports
    /******/     __webpack_require__.d = function(exports, name, getter) {
    /******/         if(!__webpack_require__.o(exports, name)) {
    /******/             Object.defineProperty(exports, name, {
    /******/                 configurable: false,
    /******/                 enumerable: true,
    /******/                 get: getter
    /******/             });
    /******/         }
    /******/     };
    /******/
    /******/     // getDefaultExport function for compatibility with non-harmony modules
    /******/     __webpack_require__.n = function(module) {
    /******/         var getter = module && module.__esModule ?
    /******/             function getDefault() { return module['default']; } :
    /******/             function getModuleExports() { return module; };
    /******/         __webpack_require__.d(getter, 'a', getter);
    /******/         return getter;
    /******/     };
    /******/
    /******/     // Object.prototype.hasOwnProperty.call
    /******/     __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
    /******/
    /******/     // __webpack_public_path__
    /******/     __webpack_require__.p = "";
    /******/
    /******/     // Load entry module and return exports
    /******/     return __webpack_require__(__webpack_require__.s = 2);
    /******/ })
    /************************************************************************/
    /******/ ([
    /* 0 */,
    /* 1 */,
    /* 2 */
    /***/ (function(module, exports, __webpack_require__) {
    
    var __vue_exports__, __vue_options__
    var __vue_styles__ = []
    
    /* styles */
    __vue_styles__.push(__webpack_require__(3)
    )
    
    /* script */
    __vue_exports__ = __webpack_require__(4)
    
    /* template */
    var __vue_template__ = __webpack_require__(5)
    __vue_options__ = __vue_exports__ = __vue_exports__ || {}
    if (
      typeof __vue_exports__.default === "object" ||
      typeof __vue_exports__.default === "function"
    ) {
    if (Object.keys(__vue_exports__).some(function (key) { return key !== "default" && key !== "__esModule" })) {console.error("named exports are not supported in *.vue files.")}
    __vue_options__ = __vue_exports__ = __vue_exports__.default
    }
    if (typeof __vue_options__ === "function") {
      __vue_options__ = __vue_options__.options
    }
    __vue_options__.__file = "/Users/mac/Desktop/GofWeexNoRoute/src/index.vue"
    __vue_options__.render = __vue_template__.render
    __vue_options__.staticRenderFns = __vue_template__.staticRenderFns
    __vue_options__._scopeId = "data-v-11c4006c"
    __vue_options__.style = __vue_options__.style || {}
    __vue_styles__.forEach(function (module) {
      for (var name in module) {
        __vue_options__.style[name] = module[name]
      }
    })
    if (typeof __register_static_styles__ === "function") {
      __register_static_styles__(__vue_options__._scopeId, __vue_styles__)
    }
    
    module.exports = __vue_exports__
    module.exports.el = 'true'
    new Vue(module.exports)
    
    
    /***/ }),
    /* 3 */  //第一部分
    /***/ (function(module, exports) {
    
    module.exports = {
      "container": {
        "alignItems": "center"
      },
      "pic": {
        "marginTop": "100",
        "width": "424",
        "height": "200"
      },
      "text": {
        "marginTop": "40",
        "fontSize": "40",
        "color": "#000000"
      }
    }
    
    /***/ }),
    /* 4 */
    /***/ (function(module, exports, __webpack_require__) {
    
    "use strict";
    
    
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    //
    // 第二部分
    module.exports = {
      data: {
        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 = '图片被点击';
          } else {
            this.title = 'Hello Weex';
          }
        }
      }
    };
    
    /***/ }),
    /* 5 */
    /***/ (function(module, exports) {
    // 第三部分
    module.exports={render:function (){var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;
      return _c('div', {
        staticClass: ["container"]
      }, [_c('image', {
        staticClass: ["pic"],
        attrs: {
          "src": "https://gw.alicdn.com/tfs/TB1yopEdgoQMeJjy1XaXXcSsFXa-640-302.png"
        },
        on: {
          "click": _vm.picClick
        }
      }), _c('text', {
        staticClass: ["text"]
      }, [_vm._v(_vm._s(_vm.title))])])
    },staticRenderFns: []}
    module.exports.render._withStripped = true
    
    /***/ })
    /******/ ]);

    看上去一堆代码,实际上也容易看懂。

    (function(modules) {
        //......
    }

    这段代码是自动加的,暂时不管。上述加粗的三部分,分别对应<style>、<script>、<template>。客户端从服务器拿到上述的JS文件之后,就会调用JS的方法createInstance(id, code, config, data)。接下来JSFramework就会调用OC的callNative方法,依次创建视图:

    2018-05-11 21:05:23.668059+0800 GofWeexDemo[23922:1755993] <Weex>[debug]WXJSCoreBridge.m:244, callCreateBody...0, {
        attr =     {
            "@styleScope" = "data-v-11c4006c";
        };
        ref = "_root";
        style =     {
            alignItems = center;
        };
        type = div;
    },
    2018-05-11 21:05:32.116927+0800 GofWeexDemo[23922:1755993] <Weex>[debug]WXJSCoreBridge.m:229, callAddElement...0, _root, {
        attr =     {
            "@styleScope" = "data-v-11c4006c";
            src = "https://gw.alicdn.com/tfs/TB1yopEdgoQMeJjy1XaXXcSsFXa-640-302.png";
        };
        event =     (
            click
        );
        ref = 9;
        style =     {
            height = 200;
            marginTop = 100;
            width = 424;
        };
        type = image;
    }, -1
    2018-05-11 21:05:32.125669+0800 GofWeexDemo[23922:1755993] <Weex>[debug]WXJSCoreBridge.m:229, callAddElement...0, _root, {
        attr =     {
            "@styleScope" = "data-v-11c4006c";
        };
        ref = 11;
        style =     {
            color = "#000000";
            fontSize = 40;
            marginTop = 40;
        };
        type = text;
    }, -1
    2018-05-11 21:05:32.127222+0800 GofWeexDemo[23922:1755993] <Weex>[debug]WXJSCoreBridge.m:292, callUpdateAttrs...0, 11, {
        value = "Hello World";
    }
    2018-05-11 21:05:34.759200+0800 GofWeexDemo[23922:1755993] <Weex>[debug]WXJSCoreBridge.m:354, callRCreateFinish...0
    {layout: { 320, height: 568, top: 0, left: 0}, flexDirection: 'column', alignItems: 'stretch', flex: 0,  320, height: 568, left: 0, top: 0, children: [
      {_root:div layout: { 320, height: 568, top: 0, left: 0}, flexDirection: 'column', alignItems: 'center', flex: 0,  320, height: 568, children: [
        {9:image layout: { 180.907, height: 85.3333, top: 42.6667, left: 69.5467}, flexDirection: 'column', alignItems: 'stretch', flex: 0, marginTop: 42.6667, marginStart: nan, marginEnd: nan,  180.907, height: 85.3333, },
        {11:text layout: { 93, height: 20.5, top: 145.067, left: 113.5}, flexDirection: 'column', alignItems: 'stretch', flex: 0, marginTop: 17.0667, marginStart: nan, marginEnd: nan, },
      ]},
    ]},

    JSFramework在整个过程中扮演的角色是根据输入的JSBundle,不断的输出Json格式的Virtual DOM,然后通过JSCore调用OC原生方法,生成View。

  • 相关阅读:
    UVA 10600 ACM Contest and Blackout(次小生成树)
    UVA 10369
    UVA Live 6437 Power Plant 最小生成树
    UVA 1151 Buy or Build MST(最小生成树)
    UVA 1395 Slim Span 最小生成树
    POJ 1679 The Unique MST 次小生成树
    POJ 1789 Truck History 最小生成树
    POJ 1258 Agri-Net 最小生成树
    ubuntu 用法
    ubuntu 搭建ftp服务器,可以通过浏览器访问,filezilla上传文件等功能
  • 原文地址:https://www.cnblogs.com/LeeGof/p/9025653.html
Copyright © 2011-2022 走看看