iOS 端Weex初体验

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的几个步骤

  1. 导入WeexSDK
    下载源代码,将/ios/sdk整个目录拷贝到项目中,用pod安装Weex
#Weex支持的最低版本是iOS7.0.
    platform :ios, '7.0'
    #指定WeexSDK所在的路径是`./sdk/`这里的`.`代表`Profile`文件所在目录
    pod 'WeexSDK', :path=>'./sdk/'
  1. 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
  1. 渲染
    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];
}
  1. 热更新策略
    客户端本地保存当前js版本以及每个js文件对应编码,每次启动都去请求接口,后端返回服务端版本与文件编码,客户端先根据js版本判断是否需要更新js文件,若需要则进一步对比js文件编码,处理新增或是修改替换。待下篇详解更新策略。

你可能感兴趣的:(iOS 端Weex初体验)