weex-iOS实战

逻辑织雪使
• 阅读 11879

前言

weex学习也有一段时间了,关于weex在三端的使用,我们也做了实战开发,渲染时间在100-300ms之间,各平台体验相比H5都有极大的提升,此文章在iOS的角度记录开发过程中遇到的一些问题,如果想要了解前端和安卓的开发可以参考我同事写的一些内容weex 实践(前端视角)weex 实践(安卓视角)

准备工作

订单页实战(weex-iOS相关)

接下来我以订单页面为例,来描述一些用到的weex相关知识点,如下图描述

weex-iOS实战

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页面的基础代码

/*通过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进行跳转

#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、alert

#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 无缝跳转

主要讲解如何实现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的目的

weex-iOS实战

预加载weex-JS页面 提高渲染速度

主要讲解提前预下载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: 最低支持的版本号

然后根据配置文件做如下操作

  1. 每次更新完配置文件,遍历,查看是否存在md5一致的page_xxx.js文件,如果不存在则更新

  2. 下载完成后,保存格式为xxx.js,校验md5
    相同的话,记录文件的最后修改时间
    不同的话,删除已下载文件,重新下载,重复校验流程

  3. 支持统一跳转协议,page对应目前app端的统一跳转协议里的page,有必要的时候可以替换原来的native页面,解决native页面错误不能及时修复的问题。加载失败的话,打开h5页面

  4. 每次打开指定页面的时候,先检查本地是否有对应page文件,再检验最后修改时间是否跟记录的一致
    一致就加载
    不一致就用线上url

    第三条提到的统一跳转协议是我们为了解耦各个模块所使用的一种方式,可根据自己的业务做相应的改变
    我们的就类似:
    showjoyshop://page.sh/weex
    showjoyshop://page.sh/webview
    weex对应的就是weex的vc webview对应的就是webview的vc  weex和webview即是第三条提到的page
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Karen110 Karen110
3年前
一篇文章带你了解JavaScript日期
日期对象允许您使用日期(年、月、日、小时、分钟、秒和毫秒)。一、JavaScript的日期格式一个JavaScript日期可以写为一个字符串:ThuFeb02201909:59:51GMT0800(中国标准时间)或者是一个数字:1486000791164写数字的日期,指定的毫秒数自1970年1月1日00:00:00到现在。1\.显示日期使用
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
1年前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Easter79 Easter79
3年前
Taro小程序自定义顶部导航栏
微信自带的顶部导航栏是无法支持自定义icon和增加元素的,在开发小程序的时候自带的根本满足不了需求,分享一个封装好的组件,支持自定义icon、扩展dom,适配安卓、ios、h5,全面屏。我用的是京东的Taro多端编译框架写的小程序,原生的也可以适用,用到的微信/taro的api做调整就行,实现效果如下。!在这里插入图片描述(https://i
Stella981 Stella981
3年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable