Weex是如何让JS调用产生原生UIView的?

时间:2021-06-21 21:48:53

从官方的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: 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 != ) {
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, ), ^{
NSData *fileData = [[NSFileManager defaultManager] contentsAtPath:[url path]];
if (self.onFinished) {
self.onFinished([[WXResourceResponse alloc]initWithURL:url statusCode: 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 > ) {
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 != ) {
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:];
}
}
__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:];
}
} //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是如何让JS调用产生原生UIView的?

下面我们通过一个示例来说明一下上面的过程。 首先我们在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>

运行效果大致如下:

Weex是如何让JS调用产生原生UIView的?

上面的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。

Weex是如何让JS调用产生原生UIView的?