weex学习也有一段时间了,关于weex在三端的使用,咱们也作了实战开发,渲染时间在100-300ms之间,各平台体验相比H5都有极大的提高,此文章在iOS的角度记录开发过程当中遇到的一些问题,若是想要了解前端和安卓的开发能够参考我同事写的一些内容weex 实践(前端视角)、weex 实践(安卓视角)html
weexSDK接入
Weex iOS SDK 官方集成指南前端
WXDevtool工具使用
Weex调试神器——Weex Devtools使用手册web
接下来我以订单页面为例,来描述一些用到的weex相关知识点,以下图描述json
1. 初始化SDK,注册module、protocol、component缓存
/* 在appDelagate里初始化weexSDK并注册module、protocol、component */ -(void)initWeex{ /* 初始化SDK环境 */ [WXSDKEngine initSDKEnviroment]; /* 自定义module*/ [WXSDKEngine registerModule:@"shopBase" withClass:[BaseModule class]]; [WXSDKEngine registerModule:@"shopModal" withClass:[WXModuleAnno class]]; /* 初始化Protocol*/ [WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)]; [WXSDKEngine registerHandler:[WXSJNetworkDefaultlmpl new] withProtocol:@protocol(WXNetworkProtocol)]; /* 初始化Component*/ [WXSDKEngine registerComponent:@"a" withClass:NSClassFromString(@"WXPushComponent")]; }
2. 实现相似选项卡的效果
如图片第一点描述同一个viewcontroller多个view间的切换,此处本店订单和个人订单为不一样的view,点击来回切换,达到相似选项卡的效果
先贴段渲染weex页面的基础代码weex
/*经过JS连接渲染weex页面 会产出一个view*/ -(void)renderWeexWithUrl:(NSString *)url{ _instance = [[WXSDKInstance alloc] init]; _instance.viewController = self; CGFloat width = self.view.frame.size.width; _instance.frame = CGRectMake(self.view.frame.size.width-width, 0, width, _weexHeight); _instance.onCreate = ^(UIView *view) { /*页面渲染成功 会产出一个view*/ }; _instance.onFailed = ^(NSError *error) { }; _instance.renderFinish = ^(UIView *view) { }; _instance.updateFinish = ^(UIView *view) { }; [_instance renderWithURL:[NSURL URLWithString:url] options:@{@"bundleUrl":url} data:nil]; }
如上所述 咱们能够针对产出的view进行处理,简单的页面直接添加到self.view上便可。
假如须要多个view间的切换,就如订单页的tabbar切换,我这里作了以下处理:
把每次新产生的view存到一个字典里,key是连接 value是新产生view ,每次渲染页面前先经过key查找是否已经存在该view,若是已存在把存的view拿出来展现,不存在渲染出来新的view
代码修改以下网络
-(void)renderWeexWithUrl:(NSString *)url{ /*经过url查找是否已经存在该view 已存在显示出来已有的 再也不从新渲染*/ if ([self.mdicViews objectForKey:url] && [[self.mdicViews objectForKey:url] isKindOfClass:[UIView class]]) { [self loadViewforKey:url]; }else{ __weak typeof(self) weakSelf = self; _instance = [[WXSDKInstance alloc] init]; _instance.viewController = self; CGFloat width = self.view.frame.size.width; _instance.frame = CGRectMake(self.view.frame.size.width-width, 0, width, _weexHeight); _instance.onCreate = ^(UIView *view) { /*页面渲染成功 会产出一个view*/ [weakSelf.mdicViews setValue:view forKey:url]; [weakSelf loadViewforKey:url]; }; _instance.onFailed = ^(NSError *error) { }; _instance.renderFinish = ^(UIView *view) { }; _instance.updateFinish = ^(UIView *view) { }; [_instance renderWithURL:[NSURL URLWithString:url] options:@{@"bundleUrl":url} data:nil]; } } /*经过key显示某个view的操做*/ -(void)loadViewforKey:(NSString *)mstrJs{ self.weexView = [_mdicViews objectForKey:mstrJs]; [self.view insertSubview:self.weexView atIndex:0]; UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, self.weexView); for (int i=0; i<self.view.subviews.count; i++) { UIView * mview = [self.view.subviews objectAtIndex:i]; if (i==0) { mview.hidden=NO; }else{ mview.hidden=YES; } } }
3. 自定义a标签component 拦截url进行跳转session
#import <WeexSDK/WXComponent.h> @interface WXPushComponent : WXComponent <UIGestureRecognizerDelegate> @end #import "WXPushComponent.h" @interface WXPushComponent() @property (nonatomic, strong) UITapGestureRecognizer *tap; @property (nonatomic, strong) NSString *href; @end @implementation WXPushComponent - (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance { self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]; if (self) { _tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(openURL)]; _tap.delegate = self; if (attributes[@"href"]) { _href = attributes[@"href"]; } } return self; } - (void)dealloc { if (_tap.delegate) { _tap.delegate = nil; } } - (void)viewDidLoad { [self.view addGestureRecognizer:_tap]; } - (void)openURL { if (_href && [_href length] > 0) { /* a标签的跳转链接 能够根据该连接 进行跳转 */ } } - (void)updateAttributes:(NSDictionary *)attributes { if (attributes[@"href"]) { _href = attributes[@"href"]; } } #pragma mark #pragma gesture delegate - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) { return YES; } return NO; } @end
4. 自定义module实现confirm、toast、alertapp
#import <Foundation/Foundation.h> #import <WeexSDK/WXModuleProtocol.h> #import <WeexSDK/WeexSDK.h> @interface WXModuleAnno : NSObject<WXModuleProtocol> @end #import "WXModuleAnno.h" @implementation WXModuleAnno @synthesize weexInstance; WX_EXPORT_METHOD(@selector(toast:)) WX_EXPORT_METHOD(@selector(alert:callback:)) WX_EXPORT_METHOD(@selector(confirm:callback:)) - (void)confirm:(NSDictionary *)param callback:(WXModuleCallback)callback { NSString *message = [self stringValue:param[@"message"]]; NSString *okTitle = [self stringValue:param[@"okTitle"]]; NSString *cancelTitle = [self stringValue:param[@"cancelTitle"]]; if (okTitle.length==0) { okTitle = @"确认"; } if (cancelTitle.length==0) { cancelTitle = @"取消"; } /* 此处为本身的弹框组件或者系统的组件 */ /**/ callback(okTitle); } - (void)toast:(NSDictionary *)param{ NSString *message = [NSString stringWithFormat:@"%@",param[@"message"]]; if (!message) return; /* 此处为本身的toast 组件 */ /**/ } - (void)alert:(NSDictionary *)param callback:(WXModuleCallback)callback { NSString *message = [self stringValue:param[@"message"]]; NSString *okTitle = [self stringValue:param[@"okTitle"]]; /* 此处为本身的弹框组件或者系统的组件 */ /**/ callback(okTitle); } // 获取当前NVC -(UINavigationController *)currentNVC{ return [weexInstance.viewController navigationController]; } // 获取当前VC -(UIViewController *)currentVC{ return weexInstance.viewController; } - (NSString*)stringValue:(id)value { if ([value isKindOfClass:[NSString class]]) { return value; } if ([value isKindOfClass:[NSNumber class]]) { return [value stringValue]; } return nil; } @end
5. 自定义图片加载protocol,能够对图片进行压缩和缓存的处理工具
#import <Foundation/Foundation.h> #import <WeexSDK/WXImgLoaderProtocol.h> @interface WXImgLoaderDefaultImpl : NSObject<WXImgLoaderProtocol> @end #import "WXImgLoaderDefaultImpl.h" #import <SDWebImage/UIImageView+WebCache.h> @interface WXImgLoaderDefaultImpl() @end @implementation WXImgLoaderDefaultImpl #pragma mark - #pragma mark WXImgLoaderProtocol - (id<WXImageOperationProtocol>)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)userInfo completed:(void(^)(UIImage *image, NSError *error, BOOL finished))completedBlock { if ([url hasPrefix:@"jpg"] || [url hasPrefix:@"png"]) { /* 作相应的处理 */ } return (id<WXImageOperationProtocol>)[[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
6. 自定义NetworkProtocol,能够针对网络请求进行拦截修改
#import <Foundation/Foundation.h> #import <WeexSDK/WeexSDK.h> @interface WXSJNetworkDefaultlmpl : NSObject<WXNetworkProtocol, NSURLSessionDelegate> @end #import "WXSJNetworkDefaultlmpl.h" @interface WXNetworkCallbackInfo : NSObject @property (nonatomic, copy) void(^sendDataCallback)(int64_t, int64_t); @property (nonatomic, copy) void(^responseCallback)(NSURLResponse *); @property (nonatomic, copy) void(^receiveDataCallback)(NSData *); @property (nonatomic, strong) NSMutableData *data; @property (nonatomic, copy) void(^compeletionCallback)(NSData *, NSError *); @end @implementation WXSJNetworkDefaultlmpl { NSMutableDictionary *_callbacks; NSURLSession *_session; } - (id)sendRequest:(NSURLRequest *)request withSendingData:(void (^)(int64_t, int64_t))sendDataCallback withResponse:(void (^)(NSURLResponse *))responseCallback withReceiveData:(void (^)(NSData *))receiveDataCallback withCompeletion:(void (^)(NSData *, NSError *))compeletionCallback { /*拦截了URL 若是没有域名时 添加上域名 为了保持三端同步使用 咱们域名放在每一个端添加*/ if (![request.URL.absoluteString hasPrefix:@"http"]) { request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@",@"",request.URL.absoluteString]]]; } WXNetworkCallbackInfo *info = [WXNetworkCallbackInfo new]; info.sendDataCallback = sendDataCallback; info.responseCallback = responseCallback; info.receiveDataCallback = receiveDataCallback; info.compeletionCallback = compeletionCallback; if (!_session) { _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]]; } NSURLSessionDataTask *task = [_session dataTaskWithRequest:request]; if (!_callbacks) { _callbacks = [NSMutableDictionary dictionary]; } [_callbacks setObject:info forKey:task]; [task resume]; return task; } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { WXNetworkCallbackInfo *info = [_callbacks objectForKey:task]; if (info.sendDataCallback) { info.sendDataCallback(totalBytesSent, totalBytesExpectedToSend); } } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler { WXNetworkCallbackInfo *info = [_callbacks objectForKey:task]; if (info.responseCallback) { info.responseCallback(response); } completionHandler(NSURLSessionResponseAllow); } - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)task didReceiveData:(NSData *)data { WXNetworkCallbackInfo *info = [_callbacks objectForKey:task]; if (info.receiveDataCallback) { info.receiveDataCallback(data); } NSMutableData *mutableData = info.data; if (!mutableData) { mutableData = [NSMutableData new]; info.data = mutableData; } [mutableData appendData:data]; } - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { WXNetworkCallbackInfo *info = [_callbacks objectForKey:task]; if (info.compeletionCallback) { info.compeletionCallback(info.data, error); } [_callbacks removeObjectForKey:task]; } @end
主要讲解如何实现weex native webview间的跳转,达到能够不只随意跳转而且能够替换native页面的效果
如下内容来源于我司安卓大神weex 实践(安卓视角)
App的跳转规则的weex支持方案设计
跳转规则以下图,若是看不清,能够到新页面放大查看,主要介绍一下两个配置参数:
1.参数interceptUrlList能够动态配置须要拦截的h5连接,而后生成统一跳转地址 showjoyshop://page.sh/order
示例以下:
[ { "page":"order", "url":"https://dshdjshjbx" }, { "page":"detail", "url":"https://dsdsds" } ]
2.而后经过order在参数weexPages里查找对应的js信息,而后渲染
示例以下:
[ { "page":"order", "url":"https://dshdjshjbx.js", "md5":"323827382huwhdjshdjs", "h5":"http://dsds.html" "v":"1.5.0" }, { "page":"detail", "url":"https://dsdsds.js", "md5":"323827382huwhdjshdjs", "h5":"http://dsds.html" "v":"1.5.0" } ]
url: 须要渲染的js
md5: js文件的md5值用于校验
h5: 渲染失败后的降级方案
v: 最低支持的版本号
这样就达到了动态拦截,动态上线weex的目的
主要讲解提早预下载JS文件的逻辑(固然也能够不预下载,直接使用js连接便可)
为了提高渲染效率,咱们会提早把js文件下载到本地,使用时直接加载本地文件,下载逻辑以下:
首先咱们会有一个地方录入以下格式的json数据
[ { "page":"页面名称", "url":"js下载连接", "md5":"js文件MD5", "h5":"对应的h5页面" "v":"版本号" }, { "page":"shoporder", "url":"https://xxxx.js", "md5":"js文件MD5", "h5":"http://xxxx.html" "v":"1.7.0" } ]
page: 对应统一跳转的 path(暂为页面名称)
url: 须要渲染的js
md5: js文件的md5值用于校验
h5: 渲染失败后的降级方案
v: 最低支持的版本号
而后根据配置文件作以下操做
每次更新完配置文件,遍历,查看是否存在md5一致的page_xxx.js文件,若是不存在则更新
下载完成后,保存格式为xxx.js,校验md5
相同的话,记录文件的最后修改时间
不一样的话,删除已下载文件,从新下载,重复校验流程
支持统一跳转协议,page对应目前app端的统一跳转协议里的page,有必要的时候能够替换原来的native页面,解决native页面错误不能及时修复的问题。加载失败的话,打开h5页面
每次打开指定页面的时候,先检查本地是否有对应page文件,再检验最后修改时间是否跟记录的一致
一致就加载
不一致就用线上url
第三条提到的统一跳转协议是咱们为了解耦各个模块所使用的一种方式,可根据本身的业务作相应的改变 咱们的就相似: showjoyshop://page.sh/weex showjoyshop://page.sh/webview weex对应的就是weex的vc webview对应的就是webview的vc weex和webview便是第三条提到的page