Weex是一种轻量级、可扩展、高性能框架,兼顾性能与动态性于一身,让移动开发者通过简捷的前端语法写出原生级别的性能体验,并支持iOS、安卓及Web等多端部署。
开发者只需要在本地导入Weex的SDK,就可以通过HTML/CSS/JavaScript网页的这套编程语言来开发Native级别的Weex界面。这意味着可以直接用现有Web开发的编辑器和IDE的代码补全、提示、检查等功能。从而也给前端人员开发Native端,较低的开发成本和学习成本。Weex默认打的JS bundle只包含业务JS代码,体积小很多,基础JS库包含在Weex SDK中,与Facebook的ReactiveNative相比更加轻便。同时把生成的JS bundle轻松部署到服务器端,客户端通过请求来获取新的资源,以解决传统通过提交审核,不能快速迭代的问题。前端开发者,可以通过现有编译器编写.we或者.vue文件。而客户端只需要从服务端请求检测是否需要更新,获取到的JS Bundle通过WeexSDK即可解析为.js文件,从而客户端只需要处理布局和渲染即可。
iOS客户端集成WeexSDK的几个步骤
- 导入WeexSDK
下载源代码,将/ios/sdk整个目录拷贝到项目中,用pod安装Weex
#Weex支持的最低版本是iOS7.0.
platform :ios, '7.0'
#指定WeexSDK所在的路径是`./sdk/`这里的`.`代表`Profile`文件所在目录
pod 'WeexSDK', :path=>'./sdk/'
- WeexSDK初始化
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.backgroundColor = [UIColor whiteColor];
// 在这里进行初始化SDK
[self initWeexSDK];
self.window.rootViewController = [[WXRootViewController alloc] initWithRootViewController:[YMLoadWeexViewController new]];
[self.window makeKeyAndVisible];
return YES;
}
- (void)initWeexSDK
{
[WXAppConfiguration setAppGroup:@"XXXX"];
[WXAppConfiguration setAppName:@"XXXX"];
[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的初始化最为关键,主要就是注册Components,Modules,Handlers 和 执行JSFramework。
Components(组件),即客户端封装的控件,用来提供给前端人员开发使用。在WXSDKEngine初始化的时候,已经默认注册了23种Components,这些都是Weex已经帮助移动端开发者提前写好的一些通用组件,同时可以通过fireEvent调用js方法。若想实现定制化的控件,客户端开发可自定义Components,同时也需要进行注册。
//这个方法是必须要重写的
- (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstanc;
//官方提供每一个自定义的Component都可以调用fireEvent,用来调用js方法
- (void)fireEvent:(NSString *)eventName params:(NSDictionary *)params ;
#import "YMLabelComponent.h"
@implementation YMLabelComponent
WX_EXPORT_METHOD(@selector(testFunction))
- (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstanc{
self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstanc];
if (self) {
self.cssNode->style.dimensions[CSS_WIDTH] = 51;
self.cssNode->style.dimensions[CSS_HEIGHT] = 31;
}
return self;
}
-(UIView *)loadView {
UILabel *label = [[UILabel alloc] init];
label.backgroundColor = [UIColor redColor];
label.text = @"我是个测试Component";
label.textColor = [UIColor blackColor];
return label;
}
- (void)updateStyles:(NSDictionary *)styles {
}
- (void)fireEvent:(NSString *)eventName params:(NSDictionary *)params {
}
- (void)testFunction {
DLog(@"这是一个Test");
}
@end
Module 是完成一个操作的方法集合,在 Weex 的页面中,允许开发者 require 引入,调用 module 中的方法,也就是与js进行交互,WeexSDK 在启动时候,已经注册了一些内置的 module。自定义 module, 需要让自己的 class 遵循 WXModuleProtocol 这个protocol, 通过 WX_EXPORT_METHOD 这个宏暴露出需要透出到 JavaScript 调用的方法,注册 module , 就可以完成一个简单 module 的自定义。
@interface YMMapModule ()
@property (nonatomic, strong) YMMapMgr *mapMgr;
@end
@implementation YMMapModule
@synthesize weexInstance;
WX_EXPORT_METHOD(@selector(getUserLocation:callback:))
- (void)getUserLocation:(NSDictionary *)dic callback:(WXModuleCallback)callback {
[self.mapMgr startLocationCallback:callback];
}
- (YMMapMgr *)mapMgr {
if (_mapMgr == nil) {
_mapMgr = [YMMapMgr shared];
}
return _mapMgr;
}
@end
Handler 是 WeexSDK engine 中一个 service 的概念,它可以被 component、module 和其他的 handler 实现中调用。handler 更多的是针对 native 开发同学来开发和使用,在其他的 component 和 module 中被调用。调用者不关注 handler 具体的实现,只通过接口获得需要的数据或者调用对应的 handler 方法,每个 protocol对应的 handler 在 app 生命周期期间只有一个实例。
//图片下载功能
@implementation WXImgLoaderDefaultImpl
- (id)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)userInfo completed:(void(^)(UIImage *image, NSError *error, BOOL finished))completedBlock
{
if ([url hasPrefix:@"//"]) {
url = [@"http:" stringByAppendingString:url];
}
return (id)[[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (completedBlock) {
completedBlock(image, error, finished);
}
}];
}
@end
- 渲染
weex支持两种渲染模式,一种是整个界面,一种是界面某一部分.你需要给需要渲染的weex视图指定特定的URL,然后把它添加到父控件中.
@interface YMLoadWeexViewController ()
@property (nonatomic, assign) BOOL isRoot;
@property (nonatomic, strong) NSURL *jsUrl;
@property (nonatomic, strong) WXSDKInstance *instance;
@property (nonatomic, strong) UIView *weexView;
@property (nonatomic, strong) YMMapMgr *mapMgr;
@end
@implementation YMLoadWeexViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self loadWeex];
if (self.mapMgr == nil) {
self.mapMgr = [YMMapMgr shared];
}
}
- (void)loadWeex {
if (!_jsUrl) {
if (self.isRoot) {
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *rootUrlstring;
if ([YMPreloadMgr sharedMgr].userToken.length) {
rootUrlstring = [NSString stringWithFormat:@"%@/dist/weex-app.weex.js",documentPath];
} else {
rootUrlstring = [NSString stringWithFormat:@"%@/dist/app-select-user.weex.js",documentPath];
}
self.jsUrl = [NSURL fileURLWithPath:rootUrlstring];
} else {
return;
}
}
self.navigationController.navigationBarHidden = YES;
[self render];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notificationRefreshInstance:) name:@"RefreshInstance" object:nil];
}
- (void)render
{
[_instance destroyInstance];
_instance = [[WXSDKInstance alloc] init];
if([WXPrerenderManager isTaskExist:[self.jsUrl absoluteString]]){
_instance = [WXPrerenderManager instanceFromUrl:self.jsUrl.absoluteString];
}
_instance.viewController = self;
_instance.frame = CGRectMake(0, 20, [[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen] bounds].size.height -20-49);
__weak typeof(self) weakSelf = self;
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
//WXSDKInstance创建完毕
_instance.onCreate = ^(UIView *view) {
[weakSelf.weexView removeFromSuperview];
weakSelf.weexView = view;
[weakSelf.view addSubview:weakSelf.weexView];
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, weakSelf.weexView);
};
//渲染失败
_instance.onFailed = ^(NSError *error) {
[MBProgressHUD hideHUDForView:weakSelf.view animated:YES];
if ([[error domain] isEqualToString:@"1"]) {
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableString *errMsg=[NSMutableString new];
[errMsg appendFormat:@"ErrorType:%@\n",[error domain]];
[errMsg appendFormat:@"ErrorCode:%ld\n",(long)[error code]];
[errMsg appendFormat:@"ErrorInfo:%@\n", [error userInfo]];
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"render failed" message:errMsg delegate:weakSelf cancelButtonTitle:nil otherButtonTitles:@"ok", nil];
[alertView show];
});
}
};
//渲染完成
_instance.renderFinish = ^(UIView *view) {
WXLogDebug(@"%@", @"Render Finish...");
[MBProgressHUD hideHUDForView:weakSelf.view animated:YES];
[weakSelf updateInstanceState:WeexInstanceAppear];
};
if (!self.jsUrl) {
WXLogError(@"error: render url is nil");
return;
}
if([WXPrerenderManager isTaskExist:[self.jsUrl absoluteString]]){
WX_MONITOR_INSTANCE_PERF_START(WXPTJSDownload, _instance);
WX_MONITOR_INSTANCE_PERF_END(WXPTJSDownload, _instance);
WX_MONITOR_INSTANCE_PERF_START(WXPTFirstScreenRender, _instance);
WX_MONITOR_INSTANCE_PERF_START(WXPTAllRender, _instance);
[WXPrerenderManager renderFromCache:[self.jsUrl absoluteString]];
return;
}
[_instance renderWithURL:URL options:@{@"bundleUrl":self.jsUrl.absoluteString} data:nil];
}
- (void)dealloc
{
[_instance destroyInstance];
}
- 热更新策略
客户端本地保存当前js版本以及每个js文件对应编码,每次启动都去请求接口,后端返回服务端版本与文件编码,客户端先根据js版本判断是否需要更新js文件,若需要则进一步对比js文件编码,处理新增或是修改替换。待下篇详解更新策略。