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