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端显示图片。
但是这仅仅是简单的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比较稳妥,直接通过路径的话图片资源编译不进去。
前端资源目录:
ios端资源目录:
至此,图片就可以按需加载了。
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弹窗')
实际效果如下
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>
实际效果如下
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
网友评论