美文网首页
weex扩展ios

weex扩展ios

作者: sjeffery | 来源:发表于2019-02-26 15:36 被阅读7次

native如果使用大片的weex,甚至整个app都是weex的话会及其影响性能,所以在native我们采用了单页面集成的方式,将一些复杂度较低的更新比较频繁的页面采用weex开发。

1.ios集成weex开发

1.1添加WeexSDK

首先工程podfile内添加pod 'WeexSDK'后直接调用

pod install

WeexSDK内一些核心文件后续有时间会进行介绍。本次主要对ios端的集成,以及weex在ios端的扩展。

1.2pch里面添加导入头文件

#import <WeexSDK/WeexSDK.h>

1.3自定义manager类进行WeexSDK初始化

/** app组织<*/
    [WXAppConfiguration setAppGroup:@"PFWeex"];
    /** app名称<*/
    [WXAppConfiguration setAppName:@"WeexStudyDemo"];
    /** App版本<*/
    [WXAppConfiguration setAppVersion:@"1.0.0"];
    
    /** 初始化`WeexSDK`环境<*/
    [WXSDKEngine initSDKEnvironment];
    /** z自定义图片加载<*/
    [WXSDKEngine registerHandler:[WXImgHandler new] withProtocol:@protocol(WXImgLoaderProtocol)];
    /** 注册自定义模块,用于weex调用ios端功能<*/
    [WXSDKEngine registerModule:@"MyModule" withClass:NSClassFromString(@"MyModule")];
    /** 注册自定义组件,用于weex调用ios端UI<*/
    [WXSDKEngine registerComponent:@"MyComponent" withClass:NSClassFromString(@"MyComponent")];
    /** 设置日志的级别(默认的日志级别是Info) <*/
    [WXLog setLogLevel:WXLogLevelDebug];

1.4添加weex视图

由于工程是单页面集成,对于weex开发的页面,都是依托于一个ViewController。在ViewController内首先定义成员变量。

@property (nonatomic,strong)WXSDKInstance *instance;
@property (nonatomic,strong)UIView *weexView;

声明了WXSDKInstance对象instance以及一个承载weex视图的weexView。接下来将对instance以及weexView初始化。

WeexSDKManager *manager = [[WeexSDKManager alloc]init];
[manager initWeexSDK];
_instance = [[WXSDKInstance alloc]init];
_instance.viewController = self;
_instance.frame = CGRectMake(0, 100, 375, 400);
__weak typeof(self) weakSelf = self;
_instance.onCreate = ^(UIView *view) {
  NSLog(@"create Finish");
  __strong typeof (self) strongSelf = weakSelf;
  [strongSelf.weexView removeFromSuperview];
  strongSelf.weexView = view;
  [strongSelf.view addSubview:weakSelf.weexView];
};
_instance.onFailed = ^(NSError *error) {
  NSLog(@"fail");
};
_instance.renderFinish = ^(UIView *view) {
  NSLog(@"render Finish");
};
_instance.updateFinish = ^(UIView *view) {
  NSLog(@"update Finish");
};
/**加载本地js<*/
/*
NSString *str = [[NSBundle mainBundle]pathForResource:@"index" ofType:@"js"];
[_instance renderWithURL:[NSURL fileURLWithPath:str] options:@{@"bundleUrl":str} data:nil];
*/
/**加载网络js<*/
NSString *netURL = @"http://127.0.0.1/index.js";
[_instance renderWithURL:[NSURL URLWithString:netURL] options:@{@"bundleUrl":netURL} data:nil];

instance的frame设定了weex视图的加载位置。
instance提供了几个回调block,在onCreateblock中返回了一个view给我们,我们讲该view赋值给weexView,并将weexView添加到self.view上。native加载weex都是通过

renderWithURL: options: data:

加载,给定URL加载到JSBundleString然后解析。
调用本地的index.js文件可以采用bundle获取到路径然后通过fileURLWithPath获取到URL。网络的URL可以直接通过URLWithString,测试阶段可以开启mac本地服务器将index文件放在服务器即可。

这样一来weex就能正常在ios端显示了。
最开始的demo并不是下面那样子,因为ios端不能整解析图片加载,图是因为ios端扩展了图片加载所以能够正常显示,待会我们就会讲到怎么样在ios端显示图片。

image

但是这仅仅是简单的demo,很多其他的功能都需要native跟weex进行配合。适配问题,weex调用ios的功能等等。

2.weex扩展ios

2.1 ios网络图片加载

weex <image:src='网络地址'> 并不能在native端直接显示图片,ios需要实现图片下载功能。
我们在ios端定义这样一个类。

@interface WXImgHandler : NSObject<WXImgLoaderProtocol,WXModuleProtocol>

之后实现WXImgLoaderProtocol的代理方法,

- (id<WXImageOperationProtocol>)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)options completed:(void(^)(UIImage *image,  NSError *error, BOOL finished))completedBlock{
    if ([url hasPrefix:@"//"]) {
        url = [@"http:" stringByAppendingString:url];
    }
    return (id<WXImageOperationProtocol>)[[SDWebImageManager sharedManager]loadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
        
    } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
        if(completedBlock){
            completedBlock(image,error,finished);
        }
    }];
}

代理方法中我们使用SDWebImage4.3下载图片,ios端能够正常显示图片。

2.2 weex图片资源

weex图片资源存在形式是怎么样的呢?
1)每个客户端保存一份图片资源,weex通过统一调度根据平台类型的不同来访问资源。

<image :src="get_img_path()" class="logo"/>

get_img_path () {
  let platform = weex.config.env.platform
  if (platform === 'Web') {
     return require('../resources/search_delete.png')
  } else if (platform === 'android') {
    return 'https://img.haomeiwen.com/i2772950/14f3a55cab7f3444?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240'
  } else if (platform == 'iOS'){
    return 'local:///test.png'
  }
}

通过判别platform来加载图片资源,获取ios资源的时候需要加local:///。获取安卓资源的时候也需要加local:///,只不过需要去除后缀。前端的话通过require比较稳妥,直接通过路径的话图片资源编译不进去。

前端资源目录:


image

ios端资源目录:


image

至此,图片就可以按需加载了。
2)图片资源保存在服务器上,通过url下载图片。
这种方式比较简单,直接在image的src添加url即可实现。
3)保存在weex工程中。使用base64编码。
直接在image的src添加图片的base64编码。

2.3 weex调用ios方法

假如我们现在要在weex调用ios的弹框,那么我们可以这样做。

首先创建一个类。

@interface MyModule()<WXModuleProtocol>

@end

然后自定义方法

-(void)alertMe:(NSString *)name{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:name message:@"显示成功" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        NSLog(@"点击取消");
    }]];
    UIWindow *window = [UIApplication sharedApplication].keyWindow;
    [window.rootViewController presentViewController:alertController animated:YES completion:nil];
}

最后我们将该方法暴露给weex。

WX_EXPORT_METHOD(@selector(alertMe:))

在weex端我们可以这样调用。

weex.requireModule('MyModule').alertMe('weex调用ios弹窗')

实际效果如下

image

2.4 weex调用iosUI

iOS端新建类继承WXComponent,:

@interface MyComponent : WXComponent

@property (nonatomic,strong)UIView *backView;

- (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance{
    if (self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {
    }
    return self;
}

- (void)viewDidLoad{
    self.backView.backgroundColor = [UIColor redColor];
}

- (UIView *)loadView{
    return self.backView;
}

- (UIView *)backView{
    if (!_backView) {
        _backView = [UIView new];
    }
    return _backView;
}
@end

weex端直接就可以进行使用了,使用方法跟标签类似。

<MyComponent class="myClass" v-if="showiOSComponent">
  </MyComponent>

实际效果如下

image

2.5 ios传递数据给weex

创建模块,实现WXModuleProtocol代理。

WX_EXPORT_METHOD(@selector(typeSelectWithCallBack:))

- (void)typeSelectWithCallBack:(WXModuleCallback)callback{
    NSString *type = [[NSUserDefaults standardUserDefaults]objectForKey:@"TYPE"];
    callback(type);
}

weex端通过以下方式进行调用:

var module = weex.requireModule('MyModule')
var that = this
module.typeSelectWithCallBack(function (back) {
  that.type = back  /**<back就是ios端传给我们的数据*/
})

3 iOS本地缓存weex js文件

在WXSDK中 加载URL的时候

- (void)renderWithURL:(NSURL *)url options:(NSDictionary *)options data:(id)data
{
    if (!url) {
        WXLogError(@"Url must be passed if you use renderWithURL");
        return;
    }

    _scriptURL = url;
    [self _checkPageName];
    [self.apmInstance startRecord:self.instanceId];
    self.apmInstance.isStartRender = YES;
    
    self.needValidate = [[WXHandlerFactory handlerForProtocol:@protocol(WXValidateProtocol)] needValidate:url];
    WXResourceRequest *request = [WXResourceRequest requestWithURL:url resourceType:WXResourceTypeMainBundle referrer:@"" cachePolicy:NSURLRequestUseProtocolCachePolicy];
    [self _renderWithRequest:request options:options data:data];
    [WXTracingManager startTracingWithInstanceId:self.instanceId ref:nil className:nil name:WXTNetworkHanding phase:WXTracingBegin functionName:@"renderWithURL" options:@{@"bundleUrl":url?[url absoluteString]:@"",@"threadName":WXTMainThread}];
}

特别注意到请求的cachePolicy是NSURLRequestUseProtocolCachePolicy,也就意味着当ios下载到js文件后会缓存下来,当再一次调起该请求的时候发现还是使用老的js文件,不能够及时更新,这跟我们当时使用weex来进行热更新的初衷有点相违背。所以我们在工程内创建一个继承于WXSDKInstance的类,重写renderWithURL: option: data:方法。

/**<忽略警告*/
#pragma GCC diagnostic ignored "-Wundeclared-selector"

- (void)renderWithURL:(NSURL *)url options:(NSDictionary *)options data:(id)data
{
  if (!url) {
      WXLogError(@"Url must be passed if you use renderWithURL");
  return;
  }
  self.scriptURL = url;
#pragma clang diagnostic push
    
#pragma clang diagnostic ignored"-Warc-performSelector-leaks"
因为_checkPageName没有公开,所以采用respondsToSelector进行调用。
    if ([super respondsToSelector:@selector(_checkPageName)]) {
        [super performSelector:@selector(_checkPageName)];
    }
#pragma clang diagnostic pop
self.apmInstance startRecord:self.instanceId];
self.apmInstance.isStartRender = YES;
self.needValidate = [[WXHandlerFactory handlerForProtocol:@protocol(WXValidateProtocol)] needValidate:url];
/**<这里使用了NSURLRequestReloadIgnoringLocalCacheData  的cachePolicy,意思就是说忽略缓存数据每次都会进行请求,这样子服务器上的js文件一旦更新了iOS端也会显示新的*/
WXResourceRequest *request = [WXResourceRequest requestWithURL:url resourceType:WXResourceTypeMainBundle referrer:@"" cachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
/**<因为_renderWithRequest: options: data:也没有公开且有多个参数所以使用NSInvocation来分发方法*/
NSMethodSignature *sign = [[self class]instanceMethodSignatureForSelector:@selector(_renderWithRequest: options: data:)];
NSInvocation *invo = [NSInvocation invocationWithMethodSignature:sign];
invo.target = self;
invo.selector = @selector(_renderWithRequest: options: data:);
[invo setArgument:&request atIndex:2];
[invo setArgument:&options atIndex:3];
[invo setArgument:&data atIndex:4];
[invo invoke];
[WXTracingManager startTracingWithInstanceId:self.instanceId ref:nil className:nil name:WXTNetworkHanding phase:WXTracingBegin functionName:@"renderWithURL" options:@{@"bundleUrl":url?[url absoluteString]:@"",@"threadName":WXTMainThread}];
}

通过上述的操作,我们每次都可以取到最新的jsBundleString,但是有些时候js文件更新不太频繁的时候我们又不想去重新下载。这个时候我们可以将jsBundleString转成js文件缓存进cache文件夹内,不需要更新的时候直接使用该js文件,需要更新的时候下载最新的js文件然后缓存下来。
我们创建WXSDKInstance的类别,WXSDKInstance+track。提供一个

  • (void)customeRenderWithURL:(NSString *)url options:(NSDictionary *)options data:(id)data 方法供外部调用,内部方法替换掉_renderWithMainBundleString:使得我们可以对下载的jsBundleString进行缓存。具体内容如下:

#pragma GCC diagnostic ignored "-Wundeclared-selector"

@interface WXSDKInstance ()

@property (nonatomic, copy) NSString *fileName;

@end

@implementation WXSDKInstance (track)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class aClass = [self class];
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Warc-performSelector-leaks"
        SEL originalSelector = @selector(_renderWithMainBundleString:);
#pragma clang diagnostic pop
        SEL swizzledSelector = @selector(_renderWithMainBundleString_track:);
        Method originalMethod = class_getInstanceMethod(aClass, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector);

        BOOL didAddMethod =
        class_addMethod(aClass,
                        originalSelector,
                        method_getImplementation(swizzledMethod),
                        method_getTypeEncoding(swizzledMethod));

        if (didAddMethod) {
            class_replaceMethod(aClass,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

#pragma mark - Method Swizzling
- (void)_renderWithMainBundleString_track:(NSString *)mainBundleString{
    NSLog(@"mainbundleString=%@",mainBundleString);
    NSData *data = [mainBundleString dataUsingEncoding:NSUTF8StringEncoding];
    NSString *fileName = self.fileName;
    
    NSArray *cache = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *cachePath = [cache objectAtIndex:0];
    NSString *filePath = [cachePath stringByAppendingPathComponent:fileName];
    
    [data writeToFile:filePath atomically:YES];
    [self _renderWithMainBundleString_track:mainBundleString];
}


- (void)customeRenderWithURL:(NSString *)url options:(NSDictionary *)options data:(id)data{
    if (!url) {
        WXLogError(@"Url must be passed if you use customeRenderWithURL");
        return;
    }
    self.fileName = [url lastPathComponent];
    NSLog(@"name = %@",self.fileName);
    // 判断相应的逻辑,是否需要升级
    BOOL needUpdate = YES;
    if(needUpdate) {
        [self renderWithURL:[NSURL URLWithString:url] options:@{@"bundleUrl":url} data:nil];
    }
    else{
        [self loadLocalJS];
    }
}

- (void)loadLocalJS{
    NSString *localURL = [self getSaveJS];
    [self renderWithURL:[NSURL fileURLWithPath:localURL] options:@{@"bundleUrl":localURL} data: nil];
}

- (NSString *)getSaveJS{
    NSArray *cache = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *cachePath = [cache objectAtIndex:0];
    NSString *filePath = [cachePath stringByAppendingPathComponent:self.fileName];
    return filePath;
}


#pragma mark - getter setter
-(NSString *)fileName
{
    NSString *name =objc_getAssociatedObject(self, @selector(filename));
    return name;
}

-(void)setFileName:(NSString *)fileName
{
    objc_setAssociatedObject(self,@selector(filename),fileName, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end

相关文章

  • Weex iOS扩展

    Weex扩展说明 weex可扩展性,赋予了它强大的能力,它可以开放回调将部分事情交给原生去执行,也可以原生注册实体...

  • weex扩展ios

    native如果使用大片的weex,甚至整个app都是weex的话会及其影响性能,所以在native我们采用了单页...

  • Swift 中的 weex

    weex ios 集成参阅:Weex学习与实践:iOS原理篇 swift集成weex 首先将weexsdk集成到项...

  • Weex文章合集

    网易严选App感受Weex开发(已完结) Weex 是如何在 iOS 客户端上跑起来的 iOS 混合开发 —— weex

  • Weex textarea标签自动换行扩展iOS

    weex 标签扩展 实现WEEX 标签输入框自适应高度 首先贴上我的weex代码: 通过we...

  • weex_ 安居客项目实战视频教程

    weex视频教程 weex_教程,weex_Android,weex_iOS,weex 第一季 百度网盘地址:ht...

  • weex_项目实战视频教程

    weex视频教程 weex_教程,weex_Android,weex_iOS,weex 第一季 百度网盘地址:ht...

  • [个人记录]Weex入坑

    Weex入门 官方文档 文档iOS集成 开发环境配置 安装node 安装weex开发工具 验证 weex-tool...

  • Weex是如何在Android客户端上跑起来的

    参考:Weex 是如何在 iOS 客户端上跑起来的参考:Weex SDK Android 源码解析 目录 Weex...

  • weex优秀案例

    # toutiao -weex > 基于WEEX +Vue2.0仿照今日头条的app项目(android/ios)...

网友评论

      本文标题:weex扩展ios

      本文链接:https://www.haomeiwen.com/subject/xbgayqtx.html