资讯专栏INFORMATION COLUMN

weex-iOS实战

roundstones / 652人阅读

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

前言

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

准备工作

weexSDK接入
Weex iOS SDK 官方集成指南

WXDevtool工具使用
Weex调试神器——Weex Devtools使用手册

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

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

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

3. 自定义a标签component 拦截url进行跳转

#import 

@interface WXPushComponent : WXComponent 

@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 
#import 
#import 

@interface WXModuleAnno : NSObject

@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 
#import 
@interface WXImgLoaderDefaultImpl : NSObject
@end

#import "WXImgLoaderDefaultImpl.h"
#import 

@interface WXImgLoaderDefaultImpl()

@end

@implementation WXImgLoaderDefaultImpl

#pragma mark -
#pragma mark WXImgLoaderProtocol

- (id)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)[[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 
#import 
@interface WXSJNetworkDefaultlmpl : NSObject

@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-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: 最低支持的版本号

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

每次更新完配置文件,遍历,查看是否存在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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/86744.html

相关文章

  • spring boot - 收藏集 - 掘金

    摘要:引入了新的环境和概要信息,是一种更揭秘与实战六消息队列篇掘金本文,讲解如何集成,实现消息队列。博客地址揭秘与实战二数据缓存篇掘金本文,讲解如何集成,实现缓存。 Spring Boot 揭秘与实战(九) 应用监控篇 - HTTP 健康监控 - 掘金Health 信息是从 ApplicationContext 中所有的 HealthIndicator 的 Bean 中收集的, Spring...

    rollback 评论0 收藏0
  • 从小白程序员一路晋升为大厂高级技术专家我看过哪些书籍?(建议收藏)

    摘要:大家好,我是冰河有句话叫做投资啥都不如投资自己的回报率高。马上就十一国庆假期了,给小伙伴们分享下,从小白程序员到大厂高级技术专家我看过哪些技术类书籍。 大家好,我是...

    sf_wangchong 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<