preface
On April 21, 2016, Alibaba announced at Qcon that Weex, a cross-platform mobile development tool, was open for internal testing. Weex can balance performance and dynamics perfectly, allowing mobile developers to write Native level performance experience through simple front-end syntax, and supporting iOS, Android, YunOS and Web deployment.
Over the past year, cross-platform technologies such as ReactNative and Weex have had a huge impact on Native developers. There are some disadvantages in the development of Native App. For example, the client needs to be updated frequently, and the iOS update time is restricted by audit. IOS, Android and front-end simultaneously develop the same demand, which consumes a lot of personnel cost; Hybrid’s performance is a bit worse than Native’s.
ReactNative and Weex are designed to address these pain points.
More than 5,000 developers applied for the beta in just two weeks after it was announced on April 21.
On June 30, 2016, Alibaba officially announced Weex open source. Weex, a cross-platform mobile development tool that can be used to develop Native performance experiences through the Web, topped the list of trends on Github on its first day of open release. So far, Weex has reached 13,393 stars on Github. Became one of the most popular open source projects on Github in China in 2016.
directory
- 1. Weex overview
- 2.Weex working principle
- How does Weex work on iOS
- 4. Weex, ReactNative, JSPatch
Overview of Weex
Weex and ReactNative seem to be a couple from the day he was born.
ReactNative claims to “Learn once, write anywhere” while Weex claims to “Write once, Run Everywhere”. From the day Weex was born, he was expected to unify the three terminals. ReactNative supports iOS and Android, while Weex supports iOS, Android and HTML5. Unify the three ends to solve the second pain point said in the preface, at the same time the development of waste personnel costs.
Native mobile developers can use HTML/CSS/JavaScript to develop Native Weex interfaces by importing the Weex SDK locally. This means you can complete, hint, check, and so on directly with existing Web development editors and IDE code. In this way, Native terminal can be developed for front-end personnel with lower development cost and learning cost.
Weex is a lightweight, scalable, high-performance framework. Integration is also easy, and can be embedded directly in HTML5 pages or in the native UI. Because, like ReactNative, it calls Native controls, it is one level higher than Hybrid in performance. This addresses the third pain point in the introduction, performance.
Weex is lightweight, compact, and has a simple syntax, making it easy to access and use. ReactNative officially only allows the ReactNative basic JS library and business JS to be grouped into a JS bundle, and does not provide the function of subcontracting. Therefore, if you want to save traffic, you must make a subcontracting packaging tool. Weex’s default JS bundle contains only business JS code and is much smaller. The basic JS library is included in the Weex SDK. Compared with Facebook’s React Native and Microsoft’s Cordova, Weex is much lighter and smaller. The JS bundle generated by Weex can be easily deployed to the server and then pushed to the client, or the client can request new resources to complete the release. This rapid iteration solves the first pain point mentioned in the introduction, that the release cannot control the timing,
Native components and apis in Weex can be extended horizontally so that services can customize components and function modules in a decentralized manner. And also can directly reuse the Web front-end engineering management and performance monitoring tools.
Zhihu has a good comparison of Weex and ReactNative, Weex &ReactNative comparison, recommended reading.
Weex officially released V0.10.0 on February 17, 2017. This milestone is the beginning of a perfect Weex interface compatible with VUe.js.
Weex was transferred to Apache Foundation on February 24, 2017, and Alibaba will continue to iterate on infrastructure based on Apache. And enabled a new GitHub repository: github.com/apache/incu…
Therefore, the following source analysis is based on the V0.10.0 version.
Working principles of Weex
This article will not cover the principle of Weex packaging JS bundles. This article will take a closer look at how Weex works in Native. The author subdivides the principle of Native terminal again, as shown in the figure below:
Weex can use its own DSL, write. We file or. Vue file to develop the interface, the whole page writing is divided into three sections, template, style, script, using mature MVVM ideas.
Weex in terms of performance, in order to improve the performance of the client as much as possible, DSL Transformer are all implemented on the server side, Weex will transform XML + CSS + JavaScript code into JS Bundle on the server side. The Server deploys JS bundles on the Server and on the CDN.
The difference between Weex and React Native is that Weex uses the JS Framework built into the SDK to parse JS bundles downloaded from the server. This reduces the size of each JS Bundle and eliminates the need to subcontract React Native. After the client requests the JS Bundle, it will be sent to the JS Framework, which will output the Virtual DOM in Json format after the parsing. The client Native only needs to concentrate on the parsing, layout and UI rendering of the Virtual DOM. However, the logic SDK for parsing, layout, and rendering is basically implemented.
Finally, Weex supports three-terminal consistency. A JS Bundle on the server can be resolved to achieve three-terminal consistency on iOS, Android, and HTML5.
How does Weex work on iOS
After the analysis of the last chapter, we know the whole process of Weex, due to the lack of front-end knowledge of the author, so from. We or. Vue file to JS bundle front-end this part of the source analysis of this article is not involved, such as the author is familiar with the front-end, this will be made up again.
Analysis before the first point, Weex source code has actually been open source, as for the SDK Demo also relies on an ATSDK. Framework, this is not open source. Atsdk. framework is a plug-in for Weex performance monitoring.
It’s the gray box in the image above. This plug-in some large factories have their own APM, Ali temporarily did not open source this piece, but Weex all functions are not affected.
Here’s how Weex works in iOS Native. Direct source code analysis.
1. Initialize the Weex SDK
This is the first step in Native’s attempt to get Weex running.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.backgroundColor = [UIColor whiteColor];
// Initialize the SDK here
[self initWeexSDK];
self.window.rootViewController = [[WXRootViewController alloc] initWithRootViewController:[self demoController]];
[self.window makeKeyAndVisible];
return YES;
}Copy the code
In the application: didFinishLaunchingWithOptions: function to initialize the SDK. There’s a lot of initialization going on here. Now, some people might say, well, if I write the initialization here, and I initialize all these things, won’t that freeze the startup time of my App? Read on with that question.
#pragma mark weex
- (void)initWeexSDK
{
[WXAppConfiguration setAppGroup:@"AliApp"];
[WXAppConfiguration setAppName:@"WeexDemo"];
[WXAppConfiguration setExternalUserAgent:@"ExternalUA"];
[WXSDKEngine initSDKEnvironment];
[WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)];
[WXSDKEngine registerHandler:[WXEventModule new] withProtocol:@protocol(WXEventModuleProtocol)];
[WXSDKEngine registerComponent:@"select" withClass:NSClassFromString(@"WXSelectComponent")];
[WXSDKEngine registerModule:@"event" withClass:[WXEventModule class]];
[WXSDKEngine registerModule:@"syncTest" withClass:[WXSyncTestModule class]].#if ! (TARGET_IPHONE_SIMULATOR)
[self checkUpdate];
#endif
#ifdef DEBUG
[self atAddPlugin];
[WXDebugTool setDebug:YES];
[WXLog setLogLevel:WXLogLevelLog];
#ifndef UITEST
[[ATManager shareInstance] show];
#endif
#else
[WXDebugTool setDebug:NO];
[WXLog setLogLevel:WXLogLevelError];
#endif
}Copy the code
The above is to be in application: didFinishLaunchingWithOptions: initialize the entire contents of the inside. Let’s take it line by line.
WXAppConfiguration is a singleton that records App configuration information.
@interface WXAppConfiguration : NSObject
@property (nonatomic.strong) NSString * appGroup;
@property (nonatomic.strong) NSString * appName;
@property (nonatomic.strong) NSString * appVersion;
@property (nonatomic.strong) NSString * externalUA;
@property (nonatomic.strong) NSString * JSFrameworkVersion;
@property (nonatomic.strong) NSArray * customizeProtocolClasses;
/** * The AppGroup name or company organization name. The default value is nil */
+ (NSString *)appGroup;
+ (void)setAppGroup:(NSString *) appGroup;
/** * The app name. The default value is CFBundleDisplayName */ in the main bundle
+ (NSString *)appName;
+ (void)setAppName:(NSString *)appName;
/ * * * app version information, the default value is the main bundle CFBundleShortVersionString * /
+ (NSString *)appVersion;
+ (void)setAppVersion:(NSString *)appVersion;
/** * the name of the user agent outside the app. All Weex headers set the user agent field. The default value is nil */
+ (NSString *)externalUserAgent;
+ (void)setExternalUserAgent:(NSString *)userAgent;
/** * JSFrameworkVersion */
+ (NSString *)JSFrameworkVersion;
+ (void)setJSFrameworkVersion:(NSString *)JSFrameworkVersion;
/* * customizeProtocolClasses */
+ (NSArray*)customizeProtocolClasses;
+ (void)setCustomizeProtocolClasses:(NSArray*)customizeProtocolClasses;
@endCopy the code
Note that all methods of WXAppConfiguration are plus class methods. The internal implementation is implemented using the singleton of WXAppConfiguration. The class method is used here for our convenience.
Now comes the actual code to initialize the SDK.
[WXSDKEngine initSDKEnvironment];Copy the code
For a concrete implementation of initialization, see the following, with a comment:
+ (void)initSDKEnvironment
{
// Log the status
WX_MONITOR_PERF_START(WXPTInitalize)
WX_MONITOR_PERF_START(WXPTInitalizeSync)
// Load local main.js
NSString *filePath = [[NSBundle bundleForClass:self] pathForResource:@"main" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
// Initialize the SDK environment
[WXSDKEngine initSDKEnvironment:script];
// Log the status
WX_MONITOR_PERF_END(WXPTInitalizeSync)
// Emulator version special code
#if TARGET_OS_SIMULATOR
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[WXSimulatorShortcutManager registerSimulatorShortcutWithKey:@"i" modifierFlags:UIKeyModifierCommand | UIKeyModifierAlternate action:^{
NSURL *URL = [NSURL URLWithString:@"http://localhost:8687/launchDebugger"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
// ...
}];
[task resume];
WXLogInfo(@"Launching browser...");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self connectDebugServer:@"ws://localhost:8687/debugger/0/renderer"];
});
}];
});
#endif
}Copy the code
This whole SDKEnvironment initial tumble into four steps, WXMonitor monitor record state, load the local main. Js, WXSDKEngine initialization, simulator WXSimulatorShortcutManager local server connection. Now, step by step.
1. WXMonitor records the status
WXMonitor is an ordinary object, it stores only the dictionary WXThreadSafeMutableDictionary a thread-safe.
@interface WXThreadSafeMutableDictionary<KeyType.ObjectType> : NSMutableDictionary
@property (nonatomic.strong) dispatch_queue_t queue;
@property (nonatomic.strong) NSMutableDictionary* dict;
@endCopy the code
When this dictionary is initialized, it initializes a queue.
- (instancetype)init
{
self = [self initCommon];
if (self) {
_dict = [NSMutableDictionary dictionary];
}
return self;
}
- (instancetype)initCommon
{
self = [super init];
if (self) {
NSString* uuid = [NSString stringWithFormat:@"com.taobao.weex.dictionary_%p".self];
_queue = dispatch_queue_create([uuid UTF8String], DISPATCH_QUEUE_CONCURRENT);
}
return self;
}Copy the code
Generate a WXThreadSafeMutableDictionary every time, there will be a memory address corresponding to the corresponding Concurrent queue.
This queue is thread safe.
- (NSUInteger)count
{
__block NSUInteger count;
dispatch_sync(_queue, ^{
count = _dict.count;
});
return count;
}
- (id)objectForKey:(id)aKey
{
__block id obj;
dispatch_sync(_queue, ^{
obj = _dict[aKey];
});
return obj;
}
- (NSEnumerator *)keyEnumerator
{
__block NSEnumerator *enu;
dispatch_sync(_queue, ^{
enu = [_dict keyEnumerator];
});
return enu;
}
- (id)copy{
__block id copyInstance;
dispatch_sync(_queue, ^{
copyInstance = [_dict copy];
});
return copyInstance;
}Copy the code
Count, objectForKey:, keyEnumerator, and copy are all synchronized operations. Dispatch_sync is used to protect thread security.
- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey
{
aKey = [aKey copyWithZone:NULL];
dispatch_barrier_async(_queue, ^{
_dict[aKey] = anObject;
});
}
- (void)removeObjectForKey:(id)aKey
{
dispatch_barrier_async(_queue, ^{
[_dict removeObjectForKey:aKey];
});
}
- (void)removeAllObjects{
dispatch_barrier_async(_queue, ^{
[_dict removeAllObjects];
});
}Copy the code
SetObject :forKey:, removeObjectForKey:, and removeAllObjects add dispatch_barrier_async to these operations.
The WXMonitor is responsible for recording the tag values of each operation and recording the reasons for success and failure. WXMonitor encapsulates macros to facilitate method calls.
#define WX_MONITOR_PERF_START(tag) [WXMonitor performancePoint:tag willStartWithInstance:nil];
#define WX_MONITOR_PERF_END(tag) [WXMonitor performancePoint:tag didEndWithInstance:nil];
#define WX_MONITOR_INSTANCE_PERF_START(tag, instance) [WXMonitor performancePoint:tag willStartWithInstance:instance];
#define WX_MONITOR_INSTANCE_PERF_END(tag, instance) [WXMonitor performancePoint:tag didEndWithInstance:instance];
#define WX_MONITOR_PERF_SET(tag, value, instance) [WXMonitor performancePoint:tag didSetValue:value withInstance:instance];
#define WX_MONITOR_INSTANCE_PERF_IS_RECORDED(tag, instance) [WXMonitor performancePoint:tag isRecordedWithInstance:instance]
// These macros correspond to the following specific methods.
+ (void)performancePoint:(WXPerformanceTag)tag willStartWithInstance:(WXSDKInstance *)instance;
+ (void)performancePoint:(WXPerformanceTag)tag didEndWithInstance:(WXSDKInstance *)instance;
+ (void)performancePoint:(WXPerformanceTag)tag didSetValue:(double)value withInstance:(WXSDKInstance *)instance;
+ (BOOL)performancePoint:(WXPerformanceTag)tag isRecordedWithInstance:(WXSDKInstance *)instance;Copy the code
The entire operation is divided into two classes, one is global operation, one is concrete operation.
typedef enum : NSUInteger {
// global
WXPTInitalize = 0,
WXPTInitalizeSync,
WXPTFrameworkExecute,
// instance
WXPTJSDownload,
WXPTJSCreateInstance,
WXPTFirstScreenRender,
WXPTAllRender,
WXPTBundleSize,
WXPTEnd
} WXPerformanceTag;Copy the code
In WXSDKInstance before initialization, all global global operations in WXMonitor WXThreadSafeMutableDictionary. When WXSDKInstance is initialized, all operations up to instance in WXPerformanceTag are placed in the performanceDict of WXSDKInstance. Note that performanceDict is not thread-safe.
Here’s an example:
+ (void)performancePoint:(WXPerformanceTag)tag willStartWithInstance:(WXSDKInstance *)instance
{
NSMutableDictionary *performanceDict = [self performanceDictForInstance:instance];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:2];
dict[kStartKey] = @(CACurrentMediaTime(*)1000);
performanceDict[@(tag)] = dict;
}Copy the code
All actions are recorded by time:
WX_MONITOR_PERF_START(WXPTInitalize)
WX_MONITOR_PERF_START(WXPTInitalizeSync)Copy the code
WXThreadSafeMutableDictionary dictionaries will deposit is similar to the data:
{
0 = {
start = "146297522.903652";
};
1 = {
start = "146578019.356428";
};
}Copy the code
The dictionary will use the operation’s tag as the key. Typically, WX_MONITOR_PERF_START and WX_MONITOR_PERF_END are paired, and WX_MONITOR_PERF_END is called after the initialization. The resulting dictionary will look like this:
{
0 = {
end = "148750673.312226";
start = "148484241.723654";
};
1 = {
end = "148950673.312226";
start = "148485865.699819";
};
}Copy the code
WXMonitor also records success and failure information:
#define WX_MONITOR_SUCCESS_ON_PAGE(tag, pageName) [WXMonitor monitoringPointDidSuccess:tag onPage:pageName];
#define WX_MONITOR_FAIL_ON_PAGE(tag, errorCode, errorMessage, pageName) \
NSError *error = [NSError errorWithDomain:WX_ERROR_DOMAIN \
code:errorCode \
userInfo:@{NSLocalizedDescriptionKey:(errorMessage? :@"No message")}]; \
[WXMonitor monitoringPoint:tag didFailWithError:error onPage:pageName];
#define WX_MONITOR_SUCCESS(tag) WX_MONITOR_SUCCESS_ON_PAGE(tag, nil)
#define WX_MONITOR_FAIL(tag, errorCode, errorMessage) WX_MONITOR_FAIL_ON_PAGE(tag, errorCode, errorMessage, nil)
// These macros correspond to the following specific methods.
+ (void)monitoringPointDidSuccess:(WXMonitorTag)tag onPage:(NSString *)pageName;
+ (void)monitoringPoint:(WXMonitorTag)tag didFailWithError:(NSError *)error onPage:(NSString *)pageName;Copy the code
I’m not going to use these functions right now, so I’m not going to parse them.
2. Load the main.js file
The SDK comes with a main.js file, which opens directly to a bunch of webpack compressed files. The source file for this file is at github.com/apache/incu… Directory. The corresponding entry file is an HTML 5 / render/native/index. Js
import { subversion } from '.. /.. /.. /package.json'
import runtime from '.. /.. /runtime'
import frameworks from '.. /.. /frameworks/index'
import services from '.. /.. /services/index'
const { init, config } = runtime
config.frameworks = frameworks
const { native, transformer } = subversion
for (const serviceName in services) {
runtime.service.register(serviceName, services[serviceName])
}
runtime.freezePrototype()
runtime.setNativeConsole()
// register framework meta info
global.frameworkVersion = native
global.transformerVersion = transformer
// init frameworks
const globalMethods = init(config)
// set global methods
for (const methodName in globalMethods) {
global[methodName] = (. args) = > {
constret = globalMethods[methodName](... args)if (ret instanceof Error) {
console.error(ret.toString())
}
return ret
}
}Copy the code
This section of JS is passed to WXSDKManager as an input parameter. It is also the JS framework on Native side.
3. Initialization of WXSDKEngine
The initialization of WXSDKEngine is key to the entire SDK initialization.
+ (void)initSDKEnvironment:(NSString *)script
{
if(! script || script.length <=0) {
WX_MONITOR_FAIL(WXMTJSFramework, WX_ERR_JSFRAMEWORK_LOAD, @"framework loading is failure!");
return;
}
Handlers register Components, Modules, Handlers
[self registerDefaults];
/ / JsFramework execution
[[WXSDKManager bridgeMgr] executeJsFramework:script];
}Copy the code
There are two things you do, registering Components, Modules, Handlers and implementing the JSFramework.
Let’s see how to register first.
+ (void)registerDefaults
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self _registerDefaultComponents];
[self _registerDefaultModules];
[self _registerDefaultHandlers];
});
}Copy the code
Components, Modules, Handlers are registered when WXSDKEngine is initialized.
Look at the Components:
+ (void)_registerDefaultComponents
{
[self registerComponent:@"container" withClass:NSClassFromString(@"WXDivComponent") withProperties:nil];
[self registerComponent:@"div" withClass:NSClassFromString(@"WXComponent") withProperties:nil];
[self registerComponent:@"text" withClass:NSClassFromString(@"WXTextComponent") withProperties:nil];
[self registerComponent:@"image" withClass:NSClassFromString(@"WXImageComponent") withProperties:nil];
[self registerComponent:@"scroller" withClass:NSClassFromString(@"WXScrollerComponent") withProperties:nil];
[self registerComponent:@"list" withClass:NSClassFromString(@"WXListComponent") withProperties:nil];
[self registerComponent:@"header" withClass:NSClassFromString(@"WXHeaderComponent")];
[self registerComponent:@"cell" withClass:NSClassFromString(@"WXCellComponent")];
[self registerComponent:@"embed" withClass:NSClassFromString(@"WXEmbedComponent")];
[self registerComponent:@"a" withClass:NSClassFromString(@"WXAComponent")];
[self registerComponent:@"select" withClass:NSClassFromString(@"WXSelectComponent")];
[self registerComponent:@"switch" withClass:NSClassFromString(@"WXSwitchComponent")];
[self registerComponent:@"input" withClass:NSClassFromString(@"WXTextInputComponent")];
[self registerComponent:@"video" withClass:NSClassFromString(@"WXVideoComponent")];
[self registerComponent:@"indicator" withClass:NSClassFromString(@"WXIndicatorComponent")];
[self registerComponent:@"slider" withClass:NSClassFromString(@"WXSliderComponent")];
[self registerComponent:@"web" withClass:NSClassFromString(@"WXWebComponent")];
[self registerComponent:@"loading" withClass:NSClassFromString(@"WXLoadingComponent")];
[self registerComponent:@"loading-indicator" withClass:NSClassFromString(@"WXLoadingIndicator")];
[self registerComponent:@"refresh" withClass:NSClassFromString(@"WXRefreshComponent")];
[self registerComponent:@"textarea" withClass:NSClassFromString(@"WXTextAreaComponent")];
[self registerComponent:@"canvas" withClass:NSClassFromString(@"WXCanvasComponent")];
[self registerComponent:@"slider-neighbor" withClass:NSClassFromString(@"WXSliderNeighborComponent")];
}Copy the code
These 23 base components are registered by default during WXSDKEngine initialization. Here’s one of the most complex components, WXWebComponent, to see how it is registered.
So the first thing I want to say is,
+ (void)registerComponent:(NSString *)name withClass:(Class)clazz
{
[self registerComponent:name withClass:clazz withProperties: @{@"append":@"tree"}];
}Copy the code
RegisterComponent: withClass: methods and registerComponent: withClass: withProperties: approach the difference between the last and whether the @ {@ “append” : @ “tree”}, If marked as @”tree”, a layout will be forced once the syncQueue is overloaded with tasks.
Container, DIV, Text, Image, Scroller and List are not marked as @”tree” among the 23 basic components. The remaining 18 components can be forced to implement a layout.
+ (void)registerComponent:(NSString *)name withClass:(Class)clazz withProperties:(NSDictionary *)properties
{
if(! name || ! clazz) {return;
}
WXAssert(name && clazz, "Fail to register the component, please check if the parameters are correct!");
// 1.WXComponentFactory Method to register a component
[WXComponentFactory registerComponent:name withClass:clazz withPros:properties];
// 2. Iterate through all asynchronous methods
NSMutableDictionary *dict = [WXComponentFactory componentMethodMapsWithName:name];
dict[@"type"] = name;
// 3. Register the component with WXBridgeManager
if (properties) {
NSMutableDictionary *props = [properties mutableCopy];
if ([dict[@"methods"] count]) {
[props addEntriesFromDictionary:dict];
}
[[WXSDKManager bridgeMgr] registerComponents:@[props]];
} else{ [[WXSDKManager bridgeMgr] registerComponents:@[dict]]; }}Copy the code
All registered components are registered through WXComponentFactory. WXComponentFactory is a singleton.
@interface WXComponentFactory : NSObject
{
NSMutableDictionary *_componentConfigs;
NSLock *_configLock;
}
@property (nonatomic.strong) NSDictionary *properties;
@endCopy the code
In WXComponentFactory, _componentConfigs stores all component configurations, and the registration process is also the process of generating _componentConfigs.
- (void)registerComponent:(NSString *)name withClass:(Class)clazz withPros:(NSDictionary *)pros
{
WXAssert(name && clazz, @"name or clazz must not be nil for registering component.");
WXComponentConfig *config = nil;
[_configLock lock];
config = [_componentConfigs objectForKey:name];
// If the component is already registered, it is prompted to repeat the registration and overrides the previous registration action
if(config){
WXLogInfo(@"Overrider component name:%@ class:%@, to name:%@ class:%@",
config.name, config.class, name, clazz);
}
config = [[WXComponentConfig alloc] initWithName:name class:NSStringFromClass(clazz) pros:pros];
[_componentConfigs setValue:config forKey:name];
// Register class methods
[config registerMethods];
[_configLock unlock];
}Copy the code
In the _componentConfigs dictionary of WXComponentFactory, component names are used as keys and WXComponentConfig is used as values to store the configuration of each component.
@interface WXComponentConfig : WXInvocationConfig
@property (nonatomic.strong) NSDictionary *properties;
@end
@interface WXInvocationConfig : NSObject
@property (nonatomic.strong) NSString *name;
@property (nonatomic.strong) NSString *clazz;
@property (nonatomic.strong) NSMutableDictionary *asyncMethods;
@property (nonatomic.strong) NSMutableDictionary *syncMethods;
@endCopy the code
WXComponentConfig inherits from WXInvocationConfig. WXInvocationConfig stores component name, class name clazz, syncMethods and asyncMethods dictionaries in the class.
Component registration The key point here is to register class methods.
- (void)registerMethods
{
Class currentClass = NSClassFromString(_clazz);
if(! currentClass) { WXLogWarning(@"The module class [%@] doesn't exit!", _clazz);
return;
}
while(currentClass ! = [NSObject class]) {
unsigned int methodCount = 0;
// Get a list of the class's methods
Method *methodList = class_copyMethodList(object_getClass(currentClass), &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
// Get the SEL string name
NSString *selStr = [NSString stringWithCString:sel_getName(method_getName(methodList[i])) encoding:NSUTF8StringEncoding];
BOOL isSyncMethod = NO;
// If the SEL name is sync, it is the synchronization method
if ([selStr hasPrefix:@"wx_export_method_sync_"]) {
isSyncMethod = YES;
// if the SEL name is not sync, it is an asynchronous method
} else if ([selStr hasPrefix:@"wx_export_method_"]) {
isSyncMethod = NO;
} else {
// Methods that do not have the wx_export_method_ prefix in their names are not exposed, and continue for the next round of filtering
continue;
}
NSString *name = nil, *method = nil;
SEL selector = NSSelectorFromString(selStr);
if ([currentClass respondsToSelector:selector]) {
method = ((NSString* (*) (id, SEL))[currentClass methodForSelector:selector])(currentClass, selector);
}
if (method.length <= 0) {
WXLogWarning(@"The Module class [%@] doesn't have any method!", _clazz);
continue;
}
// Remove the: sign from the method name
NSRange range = [method rangeOfString:@ ","];
if(range.location ! =NSNotFound) {
name = [method substringToIndex:range.location];
} else {
name = method;
}
// The final dictionary will be saved in the final method dictionary according to asynchronous and synchronous methods
NSMutableDictionary*methods = isSyncMethod ? _syncMethods : _asyncMethods; [methods setObject:method forKey:name]; } free(methodList); currentClass = class_getSuperclass(currentClass); }}Copy the code
Here, too, it is more conventional to find the corresponding class method and determine if it has “sync” in its name to determine whether the method is synchronous or asynchronous. The main thing to parse here is how component methods are exposed as class methods.
Weex exposes class methods through the WX_EXPORT_METHOD macro.
#define WX_EXPORT_METHOD(method) WX_EXPORT_METHOD_INTERNAL(method,wx_export_method_)
#define WX_EXPORT_METHOD_INTERNAL(method, token) \
+ (NSString *)WX_CONCAT_WRAPPER(token, __LINE__) { \
return NSStringFromSelector(method); The \}#define WX_CONCAT_WRAPPER(a, b) WX_CONCAT(a, b)
#define WX_CONCAT(a, b) a ## bCopy the code
The WX_EXPORT_METHOD macro fully expands to look like this:
#define WX_EXPORT_METHOD(method)
+ (NSString *)wx_export_method_ __LINE__ { \
return NSStringFromSelector(method); The \}Copy the code
For example, line 52 of the WXWebComponent reads the following line:
WX_EXPORT_METHOD(@selector(goBack))Copy the code
When the macro is precompiled, it will be expanded like this:
+ (NSString *)wx_export_method_52 {
return NSStringFromSelector(@selector(goBack));
}Copy the code
This adds a wx_export_method_52 method to the WXWebComponent class method. Since the WX_EXPORT_METHOD macro is not allowed to be written on the same line in the same file, the converted method names are bound to be different. However, there is no rule on the number of lines in different classes. The number of lines may be the same, so different classes may have the same method name.
For example, line 58 in the WXScrollerComponent
WX_EXPORT_METHOD(@selector(resetLoadmore))Copy the code
Line 58 in WXTextAreaComponent
WX_EXPORT_METHOD(@selector(focus))Copy the code
These are two different components, but the macro expands with the same method name, and the class methods of these two different classes have the same name, but it doesn’t really matter at all, because when you get a class method, you get a class_copyMethodList, and you make sure that the list has unique names.
One more thing to note is that while class_copyMethodList gets all the class methods (+ method), one might wonder if normal + method methods that are not exposed by the WX_EXPORT_METHOD macro are filtered as well.
Answer: Yes, it will be retrieved by class_copyMethodList, but there is a rule that avoids the normal + class methods that are not exposed through the WX_EXPORT_METHOD macro.
If the WX_EXPORT_METHOD macro is not used to declare an exposed normal + method, then no method with the prefix wx_export_method_ in its name is an exposed method. The code above will continue for the next round of filtering. So don’t worry about the normal + class methods coming in.
Back in the WXWebComponent registry, after retrieving the class method above, the dictionary stores the following information:
methods = {
goBack = goBack;
goForward = goForward;
reload = reload;
}Copy the code
This completes the first step in registering the component, completing the registration configuration of WXComponentConfig.
The second step in component registration iterates through all asynchronous methods.
- (NSMutableDictionary *)_componentMethodMapsWithName:(NSString *)name
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSMutableArray *methods = [NSMutableArray array];
[_configLock lock];
[dict setValue:methods forKey:@"methods"];
WXComponentConfig *config = _componentConfigs[name];
void (^mBlock)(id.id.BOOL*) = ^ (id mKey, id mObj, BOOL * mStop) {
[methods addObject:mKey];
};
[config.asyncMethods enumerateKeysAndObjectsUsingBlock:mBlock];
[_configLock unlock];
return dict;
}Copy the code
Here is still call the method of WXComponentFactory _componentMethodMapsWithName:. So this is the dictionary that iterates through the asynchronous method, puts it in the dictionary, and returns the asynchronous method.
Using the most complex WXWebComponent example, the following asynchronous method dictionary is returned:
{
methods = (
goForward,
goBack,
reload
);
}Copy the code
The final step in registering the component is to register the component in JSFrame.
@interface WXSDKManager(a)
@property (nonatomic.strong) WXBridgeManager *bridgeMgr;
@property (nonatomic.strong) WXThreadSafeMutableDictionary *instanceDict;
@endCopy the code
A WXBridgeManager is forcibly held in WXSDKManager. The WXBridgeManager is the Bridge used to interact with JS.
@interface WXBridgeManager : NSObject
@property (nonatomic.weak.readonly) WXSDKInstance *topInstance;
@property (nonatomic.strong) WXBridgeContext *bridgeCtx;
@property (nonatomic.assign) BOOL stopRunning;
@property (nonatomic.strong) NSMutableArray *instanceIdStack;
@endCopy the code
WXBridgeManager weakly references WXSDKInstance in order to be able to call WXSDKInstance properties and methods. The most important attribute in WXBridgeManager is WXBridgeContext.
@interface WXBridgeContext(a)
@property (nonatomic.weak.readonly) WXSDKInstance *topInstance;
@property (nonatomic.strong) id<WXBridgeProtocol> jsBridge;
@property (nonatomic.strong) WXDebugLoggerBridge *devToolSocketBridge;
@property (nonatomic.assign) BOOL debugJS;
// Store methods native is about to call js
@property (nonatomic.strong) NSMutableDictionary *sendQueue;
// Some stack of instances
@property (nonatomic.strong) WXThreadSafeMutableArray *insStack;
// indicates whether the JSFramework has been loaded
@property (nonatomic) BOOL frameworkLoadFinished;
// temporarily store some methods until the JSFramework is loaded
@property (nonatomic.strong) NSMutableArray *methodQueue;
// The service that stores the JS template
@property (nonatomic.strong) NSMutableArray *jsServiceQueue;
@endCopy the code
A jsBridge is strongly held in WXBridgeContext. This is the Bridge used to interact with JS.
The relationship between the three is shown in the diagram above. Because it is a weak reference, it is represented by a virtual box.
To return to the registration step, call the following method in WXSDKEngine:
[[WXSDKManager bridgeMgr] registerComponents:@[dict]];Copy the code
The WXBridgeManager calls the registerComponents method.
- (void)registerComponents:(NSArray *)components
{
if(! components)return;
__weak typeof(self) weakSelf = self;
WXPerformBlockOnBridgeThread(^(){
[weakSelf.bridgeCtx registerComponents:components];
});
}Copy the code
Finally, WXBridgeManager inside the WXBridgeContext call registerComponents, the component registration. But the step of registering the component is performed in a special thread.
void WXPerformBlockOnBridgeThread(void (^block)())
{
[WXBridgeManager _performBlockOnBridgeThread:block];
}
+ (void)_performBlockOnBridgeThread:(void (^)())block
{
if ([NSThread currentThread] == [self jsThread]) {
block();
} else{[self performSelector:@selector(_performBlockOnBridgeThread:)
onThread:[self jsThread]
withObject:[block copy]
waitUntilDone:NO]; }}Copy the code
As you can see, the block closure is executed in the thread of the jsThread, not the main thread. WXBridgeManager creates a new jsThread thread named @”com.taobao. Weex. bridge”, where all component registrations are performed. This jsThread is also a singleton and globally unique.
+ (NSThread *)jsThread
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
WXBridgeThread = [[NSThread alloc] initWithTarget:[[self class]sharedManager] selector:@selector(_runLoopThread) object:nil];
[WXBridgeThread setName:WX_BRIDGE_THREAD_NAME];
if(WX_SYS_VERSION_GREATER_THAN_OR_EQUAL_TO(@ "8.0")) {
[WXBridgeThread setQualityOfService:[[NSThread mainThread] qualityOfService]];
} else {
[WXBridgeThread setThreadPriority:[[NSThread mainThread] threadPriority]];
}
[WXBridgeThread start];
});
return WXBridgeThread;
}Copy the code
So here’s the code to create a jsThread, and a jsThread will use @selector(_runLoopThread) as a selector.
- (void)_runLoopThread
{
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while(! _stopRunning) {@autoreleasepool{[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDatedistantFuture]]; }}}Copy the code
This opens a runloop for jsThread. [NSMachPort port] [NSMachPort port] [NSMachPort port] [NSMachPort port] [NSMachPort port] [NSMachPort port] [NSMachPort port] You can only stop by yourself while. The code above is one way to write it, but the following is recommended on StackOverFlow and is the way I usually write it.
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);Copy the code
- (void)registerComponents:(NSArray *)components
{
WXAssertBridgeThread();
if(! components)return;
[self callJSMethod:@"registerComponents" args:@[components]];
}Copy the code
When you register a component in WXBridgeContext, you actually call the JS method “registerComponents”.
There is a point that needs to be noted here, because the component is registered on the child thread, so if the JSFramework is not loaded, native calls js methods will fail. Therefore, native JS methods need to be cached before the JSFramework is loaded. Once the JSFramework is loaded, all methods in the cache are thrown to the JSFramework for loading.
- (void)callJSMethod:(NSString *)method args:(NSArray *)args
{
if (self.frameworkLoadFinished) {
[self.jsBridge callJSMethod:method args:args];
} else {
[_methodQueue addObject:@{@"method":method, @"args":args}]; }}Copy the code
So we need an NSMutableArray in WXBridgeContext to cache the JS framework’s methods before they are loaded. So this is stored in the _methodQueue. If the JSFramework is loaded, the callJSMethod:args: method is called.
- (JSValue *)callJSMethod:(NSString *)method args:(NSArray *)args
{
WXLogDebug(@"Calling JS... method:%@, args:%@", method, args);
return [[_jsContext globalObject] invokeMethod:method withArguments:args];
}Copy the code
Since these registered methods are defined as global functions, it is obvious that the method should be called on the JSContext’s globalObject object. (The global function is not defined at this point in the process, as you will see later)
Using the WXWebComponent example, the method used to register the component is @ “registerComponents”, and the args parameter is as follows:
( { append = tree; methods = ( goForward, goBack, reload ); type = web; })Copy the code
In fact, the application does not execute callJSMethod:args: at this point, because the JSFramework has not been loaded yet.
The entire process for registering a component is as follows:
To register Modules
The process for registering Modules is very similar to registering Components above.
+ (void)_registerDefaultModules
{
[self registerModule:@"dom" withClass:NSClassFromString(@"WXDomModule")];
[self registerModule:@"navigator" withClass:NSClassFromString(@"WXNavigatorModule")];
[self registerModule:@"stream" withClass:NSClassFromString(@"WXStreamModule")];
[self registerModule:@"animation" withClass:NSClassFromString(@"WXAnimationModule")];
[self registerModule:@"modal" withClass:NSClassFromString(@"WXModalUIModule")];
[self registerModule:@"webview" withClass:NSClassFromString(@"WXWebViewModule")];
[self registerModule:@"instanceWrap" withClass:NSClassFromString(@"WXInstanceWrap")];
[self registerModule:@"timer" withClass:NSClassFromString(@"WXTimerModule")];
[self registerModule:@"storage" withClass:NSClassFromString(@"WXStorageModule")];
[self registerModule:@"clipboard" withClass:NSClassFromString(@"WXClipboardModule")];
[self registerModule:@"globalEvent" withClass:NSClassFromString(@"WXGlobalEventModule")];
[self registerModule:@"canvas" withClass:NSClassFromString(@"WXCanvasModule")];
[self registerModule:@"picker" withClass:NSClassFromString(@"WXPickerModule")];
[self registerModule:@"meta" withClass:NSClassFromString(@"WXMetaModule")];
[self registerModule:@"webSocket" withClass:NSClassFromString(@"WXWebSocketModule")];
}Copy the code
WXSDKEngine registers these 15 base modules by default. Take the more complex module WXWebSocketModule as an example to see how it is registered.
+ (void)registerModule:(NSString *)name withClass:(Class)clazz
{
WXAssert(name && clazz, "Fail to register the module, please check if the parameters are correct!");
// 1. WXModuleFactory Register module
NSString *moduleName = [WXModuleFactory registerModule:name withClass:clazz];
// 2. Iterate through all synchronous and asynchronous methods
NSDictionary *dict = [WXModuleFactory moduleMethodMapsWithName:moduleName];
// 3. Register the module with WXBridgeManager
[[WXSDKManager bridgeMgr] registerModules:dict];
}Copy the code
The registration module also has three steps. The first step is to register the module in the WXModuleFactory.
@interface WXModuleFactory(a)
@property (nonatomic.strong) NSMutableDictionary *moduleMap;
@property (nonatomic.strong) NSLock *moduleLock;
@endCopy the code
In the WXModuleFactory, the moduleMap stores the configuration information of all modules, and the registration process is also the process of generating the moduleMap.
- (NSString *)_registerModule:(NSString *)name withClass:(Class)clazz
{
WXAssert(name && clazz, "Fail to register the module, please check if the parameters are correct!");
[_moduleLock lock];
// It is important to note that registration modules are allowed with the same name
WXModuleConfig *config = [[WXModuleConfig alloc] init];
config.name = name;
config.clazz = NSStringFromClass(clazz);
[config registerMethods];
[_moduleMap setValue:config forKey:name];
[_moduleLock unlock];
return name;
}Copy the code
The entire registration process is to store WXModuleConfig as value and name as key into the _moduleMap dictionary.
@interface WXModuleConfig : WXInvocationConfig
@endCopy the code
WXModuleConfig simply inherits from WXInvocationConfig, so it is exactly the same as WXInvocationConfig. [config registerMethods] This method is the same as the method used to register components.
Each WXModuleConfig is logged in the WXModuleFactory:
_moduleMap = {
animation = "<WXModuleConfig: 0x60000024a230>";
canvas = "<WXModuleConfig: 0x608000259ce0>";
clipboard = "<WXModuleConfig: 0x608000259b30>";
dom = "<WXModuleConfig: 0x608000259440>";
event = "<WXModuleConfig: 0x60800025a280>";
globalEvent = "<WXModuleConfig: 0x60000024a560>";
instanceWrap = "<WXModuleConfig: 0x608000259a70>";
meta = "<WXModuleConfig: 0x60000024a7a0>";
modal = "<WXModuleConfig: 0x6080002597d0>";
navigator = "<WXModuleConfig: 0x600000249fc0>";
picker = "<WXModuleConfig: 0x608000259e60>";
storage = "<WXModuleConfig: 0x60000024a4a0>";
stream = "<WXModuleConfig: 0x6080002596e0>";
syncTest = "<WXModuleConfig: 0x60800025a520>";
timer = "<WXModuleConfig: 0x60000024a380>";
webSocket = "<WXModuleConfig: 0x608000259fb0>";
webview = "<WXModuleConfig: 0x6080002598f0>";
}Copy the code
All synchronous and asynchronous methods are logged in each WXModuleConfig.
config.name = dom,
config.clazz = WXDomModule,
config.asyncMethods = {
addElement = "addElement:element:atIndex:";
addEvent = "addEvent:event:";
addRule = "addRule:rule:";
createBody = "createBody:";
createFinish = createFinish;
getComponentRect = "getComponentRect:callback:";
moveElement = "moveElement:parentRef:index:";
refreshFinish = refreshFinish;
removeElement = "removeElement:";
removeEvent = "removeEvent:event:";
scrollToElement = "scrollToElement:options:";
updateAttrs = "updateAttrs:attrs:";
updateFinish = updateFinish;
updateStyle = "updateStyle:styles:";
},
config.syncMethods = {
}Copy the code
The second step iterates through the list of methods.
- (NSMutableDictionary *)_moduleMethodMapsWithName:(NSString *)name
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSMutableArray *methods = [self _defaultModuleMethod];
[_moduleLock lock];
[dict setValue:methods forKey:name];
WXModuleConfig *config = _moduleMap[name];
void (^mBlock)(id.id.BOOL*) = ^ (id mKey, id mObj, BOOL * mStop) {
[methods addObject:mKey];
};
[config.syncMethods enumerateKeysAndObjectsUsingBlock:mBlock];
[config.asyncMethods enumerateKeysAndObjectsUsingBlock:mBlock];
[_moduleLock unlock];
return dict;
}Copy the code
The list of methods to traverse a module is different from that of a component. First, modules have default methods.
- (NSMutableArray*)_defaultModuleMethod
{
return [NSMutableArray arrayWithObjects:@"addEventListener".@"removeAllEventListeners".nil];
}Copy the code
All modules have methods addEventListener and removeAllEventListeners. The second difference is that modules iterate over all synchronous and asynchronous methods (components iterate over only asynchronous methods). Finally returns a dictionary of all the methods that generated the module.
The DOM module, for example, returns the following dictionary:
{
dom = (
addEventListener,
removeAllEventListeners,
addEvent,
removeElement,
updateFinish,
getComponentRect,
scrollToElement,
addRule,
updateAttrs,
addElement,
createFinish,
createBody,
updateStyle,
removeEvent,
refreshFinish,
moveElement
);
}Copy the code
The last step is also to register the module in WXBridgeManager.
- (void)registerModules:(NSDictionary *)modules
{
if(! modules)return;
__weak typeof(self) weakSelf = self;
WXPerformBlockOnBridgeThread(^(){
[weakSelf.bridgeCtx registerModules:modules];
});
}Copy the code
The registration process is exactly the same as that of the component, and is performed in the jsThread of the child thread @”com.taobao. Weex. bridge”.
- (void)registerModules:(NSDictionary *)modules
{
WXAssertBridgeThread();
if(! modules)return;
[self callJSMethod:@"registerModules" args:@[modules]];
}Copy the code
The JS method name is changed to @”registerModules”, and args is the method dictionary generated in step 2.
args = ( { dom = ( addEventListener, removeAllEventListeners, addEvent, removeElement, updateFinish, getComponentRect, scrollToElement, addRule, updateAttrs, addElement, createFinish, createBody, updateStyle, removeEvent, refreshFinish, moveElement ); })Copy the code
Again, the module won’t actually be registered at this point because the JSFramework hasn’t been loaded yet, and it will also be added to the methodQueue cache.
The whole process of registration module is as follows:
And finally, register Handlers.
+ (void)_registerDefaultHandlers
{
[self registerHandler:[WXResourceRequestHandlerDefaultImpl new] withProtocol:@protocol(WXResourceRequestHandler)];
[self registerHandler:[WXNavigationDefaultImpl new] withProtocol:@protocol(WXNavigationProtocol)];
[self registerHandler:[WXURLRewriteDefaultImpl new] withProtocol:@protocol(WXURLRewriteProtocol)];
[self registerHandler:[WXWebSocketDefaultImpl new] withProtocol:@protocol(WXWebSocketHandler)];
}Copy the code
WXSDKEngine registers four handlers by default.
+ (void)registerHandler:(id)handler withProtocol:(Protocol *)protocol
{
WXAssert(handler && protocol, "Fail to register the handler, please check if the parameters are correct!");
[WXHandlerFactory registerHandler:handler withProtocol:protocol];
}Copy the code
WXSDKEngine will continue to call WXHandlerFactory registerHandler: withProtocol: method.
@interface WXHandlerFactory : NSObject
@property (nonatomic.strong) WXThreadSafeMutableDictionary *handlers;
+ (void)registerHandler:(id)handler withProtocol:(Protocol *)protocol;
+ (id)handlerForProtocol:(Protocol *)protocol;
+ (NSDictionary *)handlerConfigs;
@endCopy the code
The WXHandlerFactory is also a singleton with a thread-safe dictionary handlers that holds a mapping of instance and Protocol names.
The final step in WXSDKEngine initialization is to execute the JSFramework.
[[WXSDKManager bridgeMgr] executeJsFramework:script];Copy the code
WXSDKManager will call WXBridgeManager to execute the main.js file in the SDK.
- (void)executeJsFramework:(NSString *)script
{
if(! script)return;
__weak typeof(self) weakSelf = self;
WXPerformBlockOnBridgeThread(^(){
[weakSelf.bridgeCtx executeJsFramework:script];
});
}Copy the code
WXBridgeManager calls the executeJsFramework: method through WXBridgeContext. Here, too, method calls are made in child threads.
- (void)executeJsFramework:(NSString *)script
{
WXAssertBridgeThread();
WXAssertParam(script);
WX_MONITOR_PERF_START(WXPTFrameworkExecute);
[self.jsBridge executeJSFramework:script];
WX_MONITOR_PERF_END(WXPTFrameworkExecute);
if ([self.jsBridge exception]) {
NSString *message = [NSString stringWithFormat:@"JSFramework executes error: %@"[self.jsBridge exception]];
WX_MONITOR_FAIL(WXMTJSFramework, WX_ERR_JSFRAMEWORK_EXECUTE, message);
} else {
WX_MONITOR_SUCCESS(WXMTJSFramework);
// now the JSFramework is fully loaded
self.frameworkLoadFinished = YES;
// Execute all registered JsServices
[self executeAllJsService];
// Get the JSFramework version number
JSValue *frameworkVersion = [self.jsBridge callJSMethod:@"getJSFMVersion" args:nil];
if (frameworkVersion && [frameworkVersion isString]) {
// Save the version number to WXAppConfiguration
[WXAppConfiguration setJSFrameworkVersion:[frameworkVersion toString]];
}
// All methods cached in the _methodQueue array before execution
for (NSDictionary *method in _methodQueue) {
[self callJSMethod:method[@"method"] args:method[@"args"]];
}
[_methodQueue removeAllObjects];
// The initialization is complete.
WX_MONITOR_PERF_END(WXPTInitalize);
};
}Copy the code
WX_MONITOR_PERF_START marks WXPTFrameworkExecute before operation. After executing the JSFramework, execute with the WX_MONITOR_PERF_END tag.
- (void)executeJSFramework:(NSString *)frameworkScript
{
WXAssertParam(frameworkScript);
if (WX_SYS_VERSION_GREATER_THAN_OR_EQUAL_TO(@ "8.0")) {
[_jsContext evaluateScript:frameworkScript withSourceURL:[NSURL URLWithString:@"main.js"]];
}else{ [_jsContext evaluateScript:frameworkScript]; }}Copy the code
The core code for loading the JSFramework is here to load the JSFramework by executing evaluateScript: via JSContext. Since there is no return value, the purpose of the loaded JSFramework is simply to declare all the methods in it, not to call them. This is similar to how OC loads other frameworks, which are only loaded into memory and the methods in the Framework can be called at any time, rather than calling all of their methods as soon as they are loaded.
After loading the JSFramework, start loading the JSService and JSMethod that were previously cached. JSService is cached in jsServiceQueue. JSMethod is cached in the methodQueue.
- (void)executeAllJsService
{
for(NSDictionary *service in _jsServiceQueue) {
NSString *script = [service valueForKey:@"script"];
NSString *name = [service valueForKey:@"name"];
[self executeJsService:script withName:name];
}
[_jsServiceQueue removeAllObjects];
}Copy the code
JSService since is js into nsstrings directly, so here run directly executeJsService: withName can.
for (NSDictionary *method in _methodQueue) {
[self callJSMethod:method[@"method"] args:method[@"args"]];
}
[_methodQueue removeAllObjects];
- (JSValue *)callJSMethod:(NSString *)method args:(NSArray *)args
{
WXLogDebug(@"Calling JS... method:%@, args:%@", method, args);
NSLog(WXJSCoreBridge jsContext is about to call a method);
return [[_jsContext globalObject] invokeMethod:method withArguments:args];
}Copy the code
Since the _methodQueue contains global JS methods, you need to call invokeMethod: withArguments: to do this.
When all of this is loaded, the SDK initialization is almost complete, and the WXPTInitalize is marked.
It’s also worth noting here how jsBridge was first loaded in.
- (id<WXBridgeProtocol>)jsBridge
{
WXAssertBridgeThread();
_debugJS = [WXDebugTool isDevToolDebug];
Class bridgeClass = _debugJS ? NSClassFromString(@"WXDebugger") : [WXJSCoreBridge class];
if (_jsBridge && [_jsBridge isKindOfClass:bridgeClass]) {
return _jsBridge;
}
if (_jsBridge) {
[_methodQueue removeAllObjects];
_frameworkLoadFinished = NO;
}
_jsBridge = _debugJS ? [NSClassFromString(@"WXDebugger") alloc] : [[WXJSCoreBridge alloc] init];
[self registerGlobalFunctions];
return _jsBridge;
}Copy the code
The first time you enter this function without an instance of jsBridge, it becomes an instance of WXJSCoreBridge, and then registers the global function. The second time this function is called, _jsBridge is WXJSCoreBridge, and the following statement will not be executed again.
typedef NSInteger(^WXJSCallNative)(NSString *instance, NSArray *tasks, NSString *callback);
typedef NSInteger(^WXJSCallAddElement)(NSString *instanceId, NSString *parentRef, NSDictionary *elementData, NSInteger index);
typedef NSInvocation *(^WXJSCallNativeModule)(NSString *instanceId, NSString *moduleName, NSString *methodName, NSArray *args, NSDictionary *options);
typedef void (^WXJSCallNativeComponent)(NSString *instanceId, NSString *componentRef, NSString *methodName, NSArray *args, NSDictionary *options);Copy the code
These four closures are the four global functions that OC packages expose to JS.
- (void)registerCallNative:(WXJSCallNative)callNative
{
JSValue* (^callNativeBlock)(JSValue *, JSValue *, JSValue *) = ^JSValue*(JSValue *instance, JSValue *tasks, JSValue *callback){
NSString *instanceId = [instance toString];
NSArray *tasksArray = [tasks toArray];
NSString *callbackId = [callback toString];
WXLogDebug(@"Calling native... instance:%@, tasks:%@, callback:%@", instanceId, tasksArray, callbackId);
return [JSValue valueWithInt32:(int32_t)callNative(instanceId, tasksArray, callbackId) inContext:[JSContext currentContext]];
};
_jsContext[@"callNative"] = callNativeBlock;
}Copy the code
- (void)registerCallAddElement:(WXJSCallAddElement)callAddElement
{
id callAddElementBlock = ^(JSValue *instanceId, JSValue *ref, JSValue *element, JSValue *index, JSValue *ifCallback) {
NSString *instanceIdString = [instanceId toString];
NSDictionary *componentData = [element toDictionary];
NSString *parentRef = [ref toString];
NSInteger insertIndex = [[index toNumber] integerValue];
WXLogDebug(@"callAddElement... %@, %@, %@, %ld", instanceIdString, parentRef, componentData, (long)insertIndex);
return [JSValue valueWithInt32:(int32_t)callAddElement(instanceIdString, parentRef, componentData, insertIndex) inContext:[JSContext currentContext]];
};
_jsContext[@"callAddElement"] = callAddElementBlock;
}Copy the code
- (void)registerCallNativeModule:(WXJSCallNativeModule)callNativeModuleBlock
{
_jsContext[@"callNativeModule"] = ^JSValue *(JSValue *instanceId, JSValue *moduleName, JSValue *methodName, JSValue *args, JSValue *options) {
NSString *instanceIdString = [instanceId toString];
NSString *moduleNameString = [moduleName toString];
NSString *methodNameString = [methodName toString];
NSArray *argsArray = [args toArray];
NSDictionary *optionsDic = [options toDictionary];
WXLogDebug(@"callNativeModule... % @, % @, % @, % @", instanceIdString, moduleNameString, methodNameString, argsArray);
NSInvocation *invocation = callNativeModuleBlock(instanceIdString, moduleNameString, methodNameString, argsArray, optionsDic);
JSValue *returnValue = [JSValue wx_valueWithReturnValueFromInvocation:invocation inContext:[JSContext currentContext]];
return returnValue;
};
}Copy the code
- (void)registerCallNativeComponent:(WXJSCallNativeComponent)callNativeComponentBlock
{
_jsContext[@"callNativeComponent"] = ^void(JSValue *instanceId, JSValue *componentName, JSValue *methodName, JSValue *args, JSValue *options) {
NSString *instanceIdString = [instanceId toString];
NSString *componentNameString = [componentName toString];
NSString *methodNameString = [methodName toString];
NSArray *argsArray = [args toArray];
NSDictionary *optionsDic = [options toDictionary];
WXLogDebug(@"callNativeComponent... % @, % @, % @, % @", instanceIdString, componentNameString, methodNameString, argsArray);
callNativeComponentBlock(instanceIdString, componentNameString, methodNameString, argsArray, optionsDic);
};
}Copy the code
Because the JS method is written, multiple parameters are written in parentheses, and OC multiple parameters are separated by a: sign is not the same, so when exposed to JS, the Block needs to be wrapped again. The four methods wrapped above are then injected into the JSContext.
As shown in the image above, the blocks passed in by OC in grey, wrapped in a layer outside, become JS methods injected into JSContext.
4. The simulator WXSimulatorShortcutManager connect local debugging tools
#if TARGET_OS_SIMULATOR
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[WXSimulatorShortcutManager registerSimulatorShortcutWithKey:@"i" modifierFlags:UIKeyModifierCommand | UIKeyModifierAlternate action:^{
NSURL *URL = [NSURL URLWithString:@"http://localhost:8687/launchDebugger"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
// ...
}];
[task resume];
WXLogInfo(@"Launching browser...");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// Connect to webSocket debugger
[self connectDebugServer:@"ws://localhost:8687/debugger/0/renderer"];
});
}];
});
#endifCopy the code
Since emulators may be used in normal development, the debugging interface will be connected to the local browser (Chrome, Safari). This is when you start the emulation, launch the browser, and connect to the WebSocket debugger.
The entire process of WXSDKEngine initialization can be described as follows:
(2). Weex is how to make JS call OC native UIView?
In the previous chapter, we analyzed how WXSDKEngine is initialized. Then, after the initialization, how does the iOS Native client receive the JS page and call OC to generate UIView? Let’s make an analysis in this chapter.
Before analyzing this problem, let’s take a look at how the scanning function of WeexPlayground, an example program officially provided by Weex in the AppStore, can enter a page by scanning the QR code.
1. The principle of scanning two-dimensional code
Let’s take a look at some properties of the scan interface:
@interface WXScannerVC : UIViewController <AVCaptureMetadataOutputObjectsDelegate>
@property (nonatomic.strong) AVCaptureSession * session;
@property (nonatomic.strong) AVCaptureVideoPreviewLayer *captureLayer;
@endCopy the code
This page has no additional configuration, just some proxy that calls the camera.
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
[_captureLayer removeFromSuperlayer];
[_session stopRunning];
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
if (metadataObjects.count > 0) {
AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex : 0 ];
[selfopenURL:metadataObject.stringValue]; }}Copy the code
When scanning the qr code, agent will call the function above, scanning. The URL is metadataObject stringValue.
- (void)openURL:(NSString*)URL
{
NSString *transformURL = URL;
NSArray* elts = [URL componentsSeparatedByString:@ "?"];
if (elts.count >= 2) {
NSArray *urls = [elts.lastObject componentsSeparatedByString:@ "="];
for (NSString *param in urls) {
if ([param isEqualToString:@"_wx_tpl"]) {
transformURL = [[urls lastObject] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
break; }}}NSURL *url = [NSURL URLWithString:transformURL];
if ([self remoteDebug:url]) {
return;
}
[self jsReplace:url];
WXDemoViewController * controller = [[WXDemoViewController alloc] init];
controller.url = url;
controller.source = @"scan";
NSMutableDictionary *queryDict = [NSMutableDictionary new];
if (WX_SYS_VERSION_GREATER_THAN_OR_EQUAL_TO(@ "8.0")) {
NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
NSArray *queryItems = [components queryItems];
for (NSURLQueryItem *item in queryItems)
[queryDict setObject:item.value forKey:item.name];
}else {
queryDict = [self queryWithURL:url];
}
NSString *wsport = queryDict[@"wsport"] ?: @ "8082";
NSURL *socketURL = [NSURL URLWithString:[NSString stringWithFormat:@"ws://%@:%@", url.host, wsport]];
controller.hotReloadSocket = [[SRWebSocket alloc] initWithURL:socketURL protocols:@[@"echo-protocol"]];
controller.hotReloadSocket.delegate = controller;
[controller.hotReloadSocket open];
[[self navigationController] pushViewController:controller animated:YES];
}Copy the code
This is the completed code to open the QR code page, which contains some processing to determine the URL’s query parameter. To simplify this slightly, it looks like this:
- (void)openURL:(NSString*)URL
{
// 1. Get URL
NSString *transformURL = URL;
NSURL *url = [NSURL URLWithString:transformURL];
// 2. Configure the URL of the new page
WXDemoViewController * controller = [[WXDemoViewController alloc] init];
controller.url = url;
controller.source = @"scan";
// 3. Connect to webSocket
NSString *wsport = queryDict[@"wsport"] ?: @ "8082";
NSURL *socketURL = [NSURL URLWithString:[NSString stringWithFormat:@"ws://%@:%@", url.host, wsport]];
controller.hotReloadSocket = [[SRWebSocket alloc] initWithURL:socketURL protocols:@[@"echo-protocol"]];
controller.hotReloadSocket.delegate = controller;
[controller.hotReloadSocket open];
// 4. The page is displayed
[[self navigationController] pushViewController:controller animated:YES];
}Copy the code
OpenURL: Actually does the 4 things mentioned above. The most important thing is to configure the URL for the new interface, and connect to websocket to change. We or. Vue files so that changes can be seen on the phone in real time. The last step is the page jump. Therefore, scanning the TWO-DIMENSIONAL code can open a new page, the reason is to configure a URL for the new page, nothing more.
2. How does JS call OC native View
Back to our topic, how does JS actually call OC native View?
All the secrets are in the WXSDKInstance class.
@interface WXSDKInstance : NSObject viewController@property (nonatomic, weak) UIViewController *viewController; @property (nonatomic, strong) UIView *rootView; @property (nonatomic, strong) UIView *rootView; // If the component wants to fix the frame of the rootView, set this property to YES. Weex layout will not change the frame of the RootView. Property (nonatomic, assign) BOOL isRootViewFrozen; // Weex bundle scriptURL @property (nonatomic, strong) NSURL *scriptURL; // parent @property (nonatomic, weak) WXSDKInstance *parentInstance; @property (nonatomic, weak) NSString *parentNodeRef; @property (nonatomic, strong) NSString *instanceId; @property (nonatomic, assign) WXState state; Block @property (nonatomic, copy) void (^onCreate)(UIView *); @property (nonatomic, copy) void (^onLayoutChange)(UIView *); Block @property (nonatomic, copy) void (^renderFinish)(UIView *); Block. property (nonatomic, copy) void (^refreshFinish)(UIView *); Block @property (nonatomic, copy) void (^onFailed)(NSError *error); // A callback for weex instance page scrolling block.property (nonatomic, copy) void (^onScroll)(CGPoint contentOffset); Void (^onRenderProgress)(CGRect renderRect); // The current weex instance frame@property (nonatomic, assign) CGRect frame; @property (nonatomic, strong) NSMutableDictionary *userInfo; // CSS unit and device pixel conversion scale factor @property (nonatomic, assign, readonly) CGFloat pixelScaleFactor; // CSS unit and device pixel conversion scale factor @property (nonatomic, assign, readonly) CGFloat pixelScaleFactor; @property (nonatomic, assign)BOOL trackComponent; - (void)renderWithURL:(NSURL *)url; - (void)renderWithURL:(NSURL *)url options:(NSDictionary *)options data:(id)data; - (void)renderView:(NSString *)source options:(NSDictionary *)options data:(id)data; // forcedReload is YES, each load will be re-read from the URL, NO, it will be read from the cache - (void)reload:(BOOL)forcedReload; - (void)refreshInstance:(id)data; - (void)destroyInstance; - (id)moduleForClass:(Class)moduleClass; - (WXComponent *)componentForRef:(NSString *)ref; - (NSUInteger)numberOfComponents; - (BOOL)checkModuleEventRegistered:(NSString*)event moduleClassName:(NSString*)moduleClassName; - (void)fireModuleEvent:(Class)module eventName:(NSString *)eventName params:(NSDictionary*)params; - (void)fireGlobalEvent:(NSString *)eventName params:(NSDictionary *)params; - (NSURL *)completeURL:(NSString *)url; @endCopy the code
A WXSDKInstance corresponds to a UIViewController, so each Weex page has a corresponding WXSDKInstance.
@property (nonatomic.strong) WXSDKInstance *instance;Copy the code
WXSDKInstance is used primarily to render pages, typically by calling renderWithURL.
The active rendering process of a Weex interface is as follows:
- (void)render
{
CGFloat width = self.view.frame.size.width;
[_instance destroyInstance];
_instance = [[WXSDKInstance alloc] init];
_instance.viewController = self;
_instance.frame = CGRectMake(self.view.frame.size.width-width, 0, width, _weexHeight);
__weak typeof(self) weakSelf = self;
_instance.onCreate = ^(UIView *view) {
[weakSelf.weexView removeFromSuperview];
weakSelf.weexView = view;
[weakSelf.view addSubview:weakSelf.weexView];
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, weakSelf.weexView);
};
_instance.onFailed = ^(NSError *error) {
};
_instance.renderFinish = ^(UIView *view) {
[weakSelf updateInstanceState:WeexInstanceAppear];
};
_instance.updateFinish = ^(UIView *view) {
};
if (!self.url) {
WXLogError(@"error: render url is nil");
return;
}
NSURL *URL = [self testURL: [self.url absoluteString]];
NSString *randomURL = [NSString stringWithFormat:@"%@%@random=%d",URL.absoluteString,URL.query?@ "&":@ "?",arc4random()];
[_instance renderWithURL:[NSURL URLWithString:randomURL] options:@{@"bundleUrl":URL.absoluteString} data:nil];
}Copy the code
Since WXSDKInstance supports real-time refresh, you need to destroy the original one before creating a new one.
WXSDKInstance supports callback functions for various states. For details, see the WXSDKInstance definition above.
Weex supports loading JS files from the local PC and server. If you load it locally, you can load a JSBundle locally using the following method.
- (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];
}Copy the code
The final render a page is by calling renderWithURL: options: data: done.
- (void)renderWithURL:(NSURL *)url options:(NSDictionary *)options data:(id)data
{
if(! url) { WXLogError(@"Url must be passed if you use renderWithURL");
return;
}
WXResourceRequest *request = [WXResourceRequest requestWithURL:url resourceType:WXResourceTypeMainBundle referrer:@ "" cachePolicy:NSURLRequestUseProtocolCachePolicy];
[self _renderWithRequest:request options:options data:data];
}Copy the code
In WXSDKInstance call renderWithURL: options: data: method, generates a WXResourceRequest. NSMutableURLRequest is defined as follows:
@interface WXResourceRequest : NSMutableURLRequest
@property (nonatomic.strong) id taskIdentifier;
@property (nonatomic.assign) WXResourceType type;
@property (nonatomic.strong) NSString *referrer;
@property (nonatomic.strong) NSString *userAgent;
@endCopy the code
WXResourceRequest is basically a wrapper around NSMutableURLRequest.
Here to analyze the core function of renderWithURL: options: data: (the following code implementation based on source, slightly reducing the source code is too long, after the cut does not affect reading)
- (void)_renderWithRequest:(WXResourceRequest *)request options:(NSDictionary *)options data:(id)data;
{
NSURL *url = request.URL;
_scriptURL = url;
_options = options;
_jsData = data;
NSMutableDictionary*newOptions = [options mutableCopy] ? : [NSMutableDictionary new];
WX_MONITOR_INSTANCE_PERF_START(WXPTJSDownload, self);
__weak typeof(self) weakSelf = self;
_mainBundleLoader = [[WXResourceLoader alloc] initWithRequest:request];
// Request completed callback
_mainBundleLoader.onFinished = ^(WXResourceResponse *response, NSData *data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if ([response isKindOfClass:[NSHTTPURLResponse class]] && ((NSHTTPURLResponse*)response).statusCode ! =200) {
NSError *error = [NSError errorWithDomain:WX_ERROR_DOMAIN
code:((NSHTTPURLResponse *)response).statusCode
userInfo:@{@"message":@"status code error."}];
if (strongSelf.onFailed) {
strongSelf.onFailed(error);
}
return ;
}
if(! data) {if (strongSelf.onFailed) {
strongSelf.onFailed(error);
}
return;
}
NSString *jsBundleString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if(! jsBundleString) {return;
}
[strongSelf _renderWithMainBundleString:jsBundleString];
};
// Failed callback request
_mainBundleLoader.onFailed = ^(NSError *loadError) {
if(weakSelf.onFailed) { weakSelf.onFailed(loadError); }}; [_mainBundleLoader start]; }Copy the code
The code above does just two things. First, it generates WXResourceLoader and sets its onFinished and onFailed callbacks. The second step uses the start method.
WXResourceLoader is strongly held in WXSDKInstance. WXResourceLoader is defined as follows:
@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;
@endCopy the code
WXResourceLoader contains a WXResourceRequest, so WXResourceRequest can also see the encapsulation of network requests and provides five different state callback functions.
- (void)start
{
if ([_request.URL isFileURL]) {
[self _handleFileURL:_request.URL];
return;
}
id 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!");
}
}Copy the code
After calling the start method of WXResourceLoader, it determines if it is a local URL, and if it is a local file, it starts loading directly.
- (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 new], fileData); }}); }Copy the code
The local file calls the onFinished function directly.
If it is not a local file, start making network requests for the js file on the server side.
- (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];
}Copy the code
The network request here is just a normal NSURLSession network request.
If successful, the onFinished callback will eventually be executed.
_mainBundleLoader.onFinished = ^(WXResourceResponse *response, NSData *data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if ([response isKindOfClass:[NSHTTPURLResponse class]] && ((NSHTTPURLResponse*)response).statusCode ! =200) {
NSError *error = [NSError errorWithDomain:WX_ERROR_DOMAIN
code:((NSHTTPURLResponse *)response).statusCode
userInfo:@{@"message":@"status code error."}];
if (strongSelf.onFailed) {
strongSelf.onFailed(error);
}
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);
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;
}
WX_MONITOR_SUCCESS_ON_PAGE(WXMTJSDownload, strongSelf.pageName);
WX_MONITOR_INSTANCE_PERF_END(WXPTJSDownload, strongSelf);
[strongSelf _renderWithMainBundleString:jsBundleString];
};Copy the code
In the onFinished callback, there are three other errors: Status code error, no data return, and data converting to String failed.
NSString *jsBundleString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[strongSelf _renderWithMainBundleString:jsBundleString];Copy the code
If all is well, then in the onFinished callback you simply take the jsBundleString and render it.
- (void)_renderWithMainBundleString:(NSString *)mainBundleString
{
// The following code has been deleted to remove some errors, but does not affect reading
NSMutableDictionary *dictionary = [_options mutableCopy];
/ / generated WXRootView
WXPerformBlockOnMainThread(^{
_rootView = [[WXRootView alloc] initWithFrame:self.frame];
_rootView.instance = self;
if(self.onCreate) {
self.onCreate(_rootView); }});// Register the default modules, component Components, handlers again to make sure they are registered before instance is created
[WXSDKEngine registerDefaults];
/ / start createInstance
[[WXSDKManager bridgeMgr] createInstance:self.instanceId template:mainBundleString options:dictionary data:_jsData];
}Copy the code
Here WXSDKEngine registers modules, component Components, handlers again to make sure they are registered before instance is created.
- (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(^(){
[weakSelf.bridgeCtx createInstance:instance
template:temp
options:options
data:data];
});
}Copy the code
WXSDKManager will createInstance: in the template: options: data: method, this method must also be in JSThread execution.
- (void)createInstance:(NSString *)instance
template:(NSString *)temp
options:(NSDictionary *)options
data:(id)data
{
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 ?: @{}];
}
[self callJSMethod:@"createInstance" args:args];
}Copy the code
It’s still the JSContext call in WXJSCoreBridge
[[_jsContext globalObject] invokeMethod:method withArguments:args];Copy the code
Call JS’s “createInstance” method. From this point on, the JSFramework calls each other.
Before giving examples, let’s summarize the previous flow diagram:
See the following examples in the next section.
Here is an example of how JS can invoke an OC native View.
Write a page with JS:
<template> <div class="container"> <image src="http://9.pic.paopaoche.net/up/2016-7/201671315341.png" class="pic" onclick="picClick"></image> <text class="text">{{title}}</text> </div> </template> <style> .container{ align-items: center; } .pic{ width: 200px; height: 200px; } .text{ 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 = 'toggle '; }else{ this.title = 'Hello Weex'; } } } } </script>Copy the code
The page looks like this:
This is my. We source file. After Weex compilation, it becomes index.js.
// { "framework": "Weex" }
/ * * * * * * / (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] = {
/ * * * * * * / exports: {},
/ * * * * * * / id: moduleId,
/ * * * * * * / loaded: false
/ * * * * * * / };
/ * * * * * * / // Execute the module function
/ * * * * * * / modules[moduleId].call(module.exports, module.module.exports, __webpack_require__);
/ * * * * * * / // Flag the module as loaded
/ * * * * * * / module.loaded = 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;
/ * * * * * * / // __webpack_public_path__
/ * * * * * * / __webpack_require__.p = "";
/ * * * * * * / // Load entry module and return exports
/ * * * * * * / return __webpack_require__(0);
/ * * * * * * / })
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * * * * * / ([
/* 0 */
/ * * * / function(module, exports, __webpack_require__) {
var __weex_template__ = __webpack_require__(1)
var __weex_style__ = __webpack_require__(2)
var __weex_script__ = __webpack_require__(3)
__weex_define__('@weex-component/916f9ecb075bbff1f4ea98389a4bb514'[],function(__weex_require__, __weex_exports__, __weex_module__) {
__weex_script__(__weex_module__, __weex_exports__, __weex_require__)
if (__weex_exports__.__esModule && __weex_exports__.default) {
__weex_module__.exports = __weex_exports__.default
}
__weex_module__.exports.template = __weex_template__
__weex_module__.exports.style = __weex_style__
})
__weex_bootstrap__('@weex-component/916f9ecb075bbff1f4ea98389a4bb514'.undefined.undefined)
/ * * * / },
/ * 1 * /
/ * * * / function(module, exports) {
module.exports = {
"type": "div"."classList": [
"container"]."children": [{"type": "image"."attr": {
"src": "http://9.pic.paopaoche.net/up/2016-7/201671315341.png"
},
"classList": [
"pic"]."events": {
"click": "picClick"}}, {"type": "text"."classList": [
"text"]."attr": {
"value": function () {return this.title}
}
}
]
}
/ * * * / },
/ * 2 * /
/ * * * / function(module, exports) {
module.exports = {
"container": {
"alignItems": "center"
},
"pic": {
"width": 200."height": 200
},
"text": {
"fontSize": 40."color": "# 000000"}}/ * * * / },
/ * * / 3
/ * * * / function(module, exports) {
module.exports = function(module, exports, __weex_require__){'use strict';
module.exports = {
data: function () {return {
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 = 'Picture clicked';
} else {
this.title = 'Hello Weex'; }}}}; }/* generated by weex-loader */
/ * * * / }
/ * * * * * * / ]);Copy the code
It looks like a bunch of code, but if you take a closer look, you can see where it’s going.
(function(modules) { // webpackBootstrap... ... }Copy the code
This code is automatically added, temporarily ignored. Then there are four pieces of code, each numbered at the beginning, 0,1,2,3.
,
When the server gets the JS, OC calls the createInstance(id, code, config, data) method.
Args: (0, "(here is the JS I download on the net, because is too long, omitted)", {bundleUrl = "http://192.168.31.117:8081/HelloWeex.js"; debug = 1; })Copy the code
It then performs some transformation operations in the JSFramework:
[JS Framework] create an Weex@undefined instance from undefined [; [JS Framework] Intialize an instance with: undefined [; [JS Framework] define a component @weex-component/916f9ecb075bbff1f4ea98389a4bb514 [; [JS Framework] bootstrap for @weex-component/916f9ecb075bbff1f4ea98389a4bb514 [; [JS Framework] "init" lifecycle in Vm(916f9ecb075bbff1f4ea98389a4bb514) [; [JS Framework] "created" lifecycle in Vm(916f9ecb075bbff1f4ea98389a4bb514) [; [JS Framework] compile native component by {"type":"div","classList":["container"],"children":[{"type":"image","attr":{"src":"http://9.pic.paopaoche.net/up/2016-7/ 201671315341.png"},"classList":["pic"],"events":{"click":"picClick"}},{"type":"text","classList":["text"],"attr":{}}]} [; [JS Framework] compile to create body for div [; [JS Framework] compile to append single node for {"ref":"_root","type":"div","attr":{},"style":{"alignItems":"center"}}Copy the code
The JSFramework then calls the OC’s callNative method. Call the createBody method of the DOM module to create the rootView. The parameters are as follows:
(
{
args = (
{
attr = {
};
ref = "_root"; style = { alignItems = center; }; type = div; }); method = createBody; module = dom; })Copy the code
Once you’ve created the rootView, it’s time to continue adding the View.
[JS Framework] compile native component by {"type":"image","attr":{"src":"http://9.pic.paopaoche.net/up/2016-7/201671315341.png"},"classList":["pic"],"events":{"cl ick":"picClick"}} [; [JS Framework] compile to create element for image [; [JS Framework] compile to append single node for {"ref":"3","type":"image","attr":{"src":"http://9.pic.paopaoche.net/up/2016-7/201671315341.png"},"style":{"width":200,"h eight":200},"event":["click"]}Copy the code
JSFramework continues to add a View by calling OC’s callAddElement method. The parameters are as follows:
{
attr = {
src = "http://9.pic.paopaoche.net/up/2016-7/201671315341.png";
};
event = (
click
);
ref = 3;
style = {
height = 200;
width = 200;
};
type = image;
}Copy the code
So once you’ve added UIImage, then you’re going to add UIlabel.
[JS Framework] compile native component by {"type":"text","classList":["text"],"attr":{}} [; [JS Framework] compile to create element for text [; [JS Framework] compile to append single node for {"ref":"4","type":"text","attr":{"value":"Hello World"},"style":{"fontSize":40,"color":"#000000"}}Copy the code
JSFramework continues to add a View by calling OC’s callAddElement method. The parameters are as follows:
{
attr = {
value = "Hello World";
};
ref = 4;
style = {
color = "# 000000";
fontSize = 40;
};
type = text;
}Copy the code
When ready:
[JS Framework] "ready" lifecycle in Vm(916f9ecb075bbff1f4ea98389a4bb514)Copy the code
JSFramework continues to call OC’s callNative method with the following arguments:
(
{
args = (
4,
{
value = "hello Weex"; }); method = updateAttrs; module = dom; })Copy the code
At this point, all the layout is complete. The JSFramework will continue to call OC’s callNative method.
( { args = ( ); method = createFinish; module = dom; })Copy the code
At this point, all views have been created. The final layout is as follows:
{layout: {width: 414, height: 672, top: 0, left: 0}, flexDirection: 'column', alignItems: 'stretch', flex: 0, width: 414, height: 672, left: 0, top: 0, children: [
{_root:div layout: {width: 414, height: 672, top: 0, left: 0}, flexDirection: 'column', alignItems: 'center', flex: 0, width: 414, height: 672, children: [
{3:image layout: {width: 110.4, height: 110.4, top: 0, left: 151.8}, flexDirection: 'column', alignItems: 'stretch', flex: 0, width: 110.4, height: 110.4}, {4:text layout: {width: 107.333, height: 26.6667, top: 110.4, left: 153.333}, flexDirection: 'column', alignItems: 'stretch', flex: 0}}},],]Copy the code
From the final layout, we can see that each Module and component has its unique ID.
The next step is to update the image with WXImageComponent. After the update, the entire Render is complete.
The JSFramework’s role in the whole process is to continuously output the Virtual DOM in Json format based on the input JSBundle, and then call the OC native method through JSCore to generate the View.
In the example above, the JSFramework is basically shown in action. The general process is as follows:
The following is a detailed summary of how the JSFramework works in the Native end.
-
First, the JSFramework is initialized only once when the App is started, and multiple pages share this copy of the JSFramework. This design also improves the opening speed of all Weex pages. The JS Framework startup process is hundreds of milliseconds, which is equivalent to each time a page is opened, these hundreds of milliseconds are saved.
-
How does Weex prevent multiple Weex from interacting with each other in the same JSFramework Runtime? The Weex takes two measures. First, a globally unique INSTANCE ID must be created for each Weex page. The INSTANCE ID can directly correspond to a Weex page. Second, when JS and Native call each other, the first parameter of each method is required to be ID. Such as createInstance(ID, code, config, data), sendTasks(ID, tasks), receiveTasks(ID, tasks). In this way, the state of the different pages is isolated into different closures without affecting each other.
-
The createInstance(ID, code, config, data) method is called when Native needs to render the page. The code parameter is the String converted by the JS Bundle. Once the JSFramework receives this entry, it will parse and start sendTasks(ID, tasks).
-
SendTasks (ID, Tasks) calls the OC Native method via JSBridge. The Tasks section specifies the module name, method name, and parameters of the function. Such as:
sendTasks(id, [{ module: 'dom', method: 'removeElement', args: [elementRef]}])Copy the code
The OC method that was previously registered with JSContext is called.
-
The receiveTasks(ID, Tasks) method is also called by the client, which invokes the JS methods. ReceiveTasks have two types. One is fireEvent, which corresponds to the event triggered by the client on a DOM element, such as fireEvent(titleElementRef, ‘click’, eventObject). For example, we send an HTTP request to the Native end through the FETCH interface and set a callback function. At this time, a callbackID, such as the string “123”, will be generated on the JS end for the callback function. This is the callbackID that is sent to Native. When the request is completed, Native needs to return the result of the request to the JS Runtime. In order to match the result, this callback will eventually become a format similar to callback(callbackID, result).
Weex, ReactNative, JSPatch
This chapter is not included in this article originally, but due to the recent audit of Apple, it has brought some audit disturbance, so I plan to mention it here.
By the time you read this article, the pure ReactNative and pure Weex projects have been approved perfectly, and JSPatch is still banned.
Since this article looks at how Weex works, let’s talk a little about the differences between RN, Weex, and JSpatch.
First of all, all three are JS based hot fixes, but one of the biggest differences between RN, Weex and JSPatch is that some of Native’s methods cannot be implemented in RN or Weex interfaces if Native does not provide a method interface that JS can call.
However, JSPatch is different. Although it is also a set of Bridge based on JSCore, it is based on Runtime. The Runtime based on OC can realize various requirements.
RN and Weex are only moderately capable in terms of hot update capabilities, whereas JSPatch is pretty much anything, the Runtime does everything, it does everything.
Therefore, from the perspective of hot update capability, neither RN nor Weex can change Native code, nor dynamically call Native system private API. So Apple approved RN and Weex.
The last
This article only explains how Weex works in iOS Native, but there is still a lot to explain about Weex. For example, how can Native pages be changed in a timely manner when a page element is changed in vue. js? How is Weex’s page rendered by FlexBox algorithm? How are front-end pages packaged into JS bundles? How are we and.vue files translated via DSL? How to use JS Runtime to write some powerful JSService? How do webpackBootstrap and weex-loader generate the final JS code, and what are the optimizations in between? …
The above problems will be explained in detail in the next series of Weex articles, I hope you give more advice!