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
一个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.\n 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: {width: 320, height: 568, top: 0, left: 0}, flexDirection: 'column', alignItems: 'stretch', flex: 0, width: 320, height: 568, left: 0, top: 0, children: [ {_root:div layout: {width: 320, height: 568, top: 0, left: 0}, flexDirection: 'column', alignItems: 'center', flex: 0, width: 320, height: 568, children: [ {9:image layout: {width: 180.907, height: 85.3333, top: 42.6667, left: 69.5467}, flexDirection: 'column', alignItems: 'stretch', flex: 0, marginTop: 42.6667, marginStart: nan, marginEnd: nan, width: 180.907, height: 85.3333, }, {11:text layout: {width: 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。