iOS 进阶
一、Hummer 拦截器
与 Android 不同,iOS 的拦截器通过注解的方式来实现,相关注解定义在 HMInterceptor 中,下面已 log 为例,实现在 LogInterceptor 文件中。
拦截器示例
// 首先注册拦截器
HM_EXPORT_INTERCEPTOR(LogInterceptor)
// 实现拦截器方法 HMLoggerProtocol
- (BOOL)handleJSLog:(NSString *)log level:(HMLogLevel)level {
NSLog(@"----- >> js log:%@",log);
return YES;
}
- (BOOL)handleNativeLog:(NSString *)log level:(HMLogLevel)level {
NSLog(@"----- >> native log:%@",log);
return YES;
}
注意:部分拦截器存在“阻断”概念:一组同一类型的拦截器,如果位于前面的拦截器处理了拦截事件(返回 YES),则后面的拦截器不会被调用。
目前支持的拦截器如下:
拦截器类型 | 调用时机 | 是否阻断 |
---|---|---|
HMInterceptorTypeEventTrack | 触发 eventLisenter 回调 | 否 |
HMInterceptorTypeImage | image 组件,即将设置图片 | 是 |
HMInterceptorTypeJSLoad | hummer.evaluateScriptWithUrl | 是 |
HMInterceptorTypeLog | console 相关 api | 否 |
HMInterceptorTypeNetwork | 创建 request,构造 resonse | 是 |
HMInterceptorTypeReporter | js 执行异常 | 是 |
HMInterceptorTypeRouter | 页面跳转 | 是 |
Namespace
namespace 的作用与 android 一致。
iOS 通过对 context 设置达到一样的效果。代码见下一节容器内部实现。
二、Hummer 容器高级特性
1. 单页面容器
开发者可选择是否继承 HMViewController,继承则有以下能力:
- 容器级别的生命周期管理。
- 自动加载,执行 js。
- 自动管理原生对象的生命周期(如 context,rootView 等)。
JS 示例
class RootView extends View {
function onAppear() {
}
function onDisappear() {
}
function onDestroy() {
}
}
Hummer.render(new RootView());
INFO
使用 HMViewController 实例或子类加载上述 js,则默认支持生命周期函数。注意,该生命周期为 iOS ViewController 的生命周期!避免在 onDestroy 做过多的事情。
Native 示例
@interface ()
// context,pageView,hmRootView 应成对应关系。
@property (nonatomic, weak) HMJSContext *context;
@property (nonatomic, strong) UIView *hmRootView;
@property (nonatomic, weak) UIView * pageView;// hummer.render()传入的视图对象。
@end
@implementation HMViewController
// 生命周期管理
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self callJSWithFunc:@"onAppear" arguments:@[]];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self callJSWithFunc:@"onDisappear" arguments:@[]];
}
- (void)dealloc {
[self callJSWithFunc:@"onDestroy" arguments:@[]];
}
// 单 hummer 视图容器
- (void)initHMRootView {
/** hummer渲染view */
self.hmRootView = [[UIView alloc] initWithFrame:self.view.bounds];
self.hmRootView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.hmRootView];
}
- (void)viewDidLoad {
// 加载 hummer 根视图
[self initHMRootView];
// 执行 脚本
[self renderWithScript:script];
}
#pragma mark -渲染脚本
- (void)renderWithScript:(NSString *)script {
if (script.length == 0) {
return;
}
//设置页面参数
NSMutableDictionary * pData = [NSMutableDictionary dictionary];
if (self.URL) {
pData[@"url"]=self.URL;
}
pData[@"params"] = self.params ?: @{};
HMJSGlobal.globalObject.pageInfo = pData;
//渲染脚本之前 注册bridge
HMJSContext *context = [HMJSContext contextInRootView:self.hmRootView];
context.nameSpace = @"test_namespace";
HM_SafeRunBlock(self.registerJSBridgeBlock,context);
//执行脚本
[context evaluateScript:script fileName:self.URL];
self.pageView = self.hmRootView.subviews.firstObject;
self.context = [[HMJSGlobal globalObject] currentContext:self.pageView.hmContext];
//发送加载完成消息
[self callJSWithFunc:@"onCreate" arguments:@[]];
}
@end
2. 嵌入式页面容器
在单容器,多 hummer 视图的场景下,只需要再声明一个 hmRootView 和 context,分别执行各自的 JS 即可。
@interface ()
@property (nonatomic, strong) UIView *hmRootView1;
@property (nonatomic, weak) HMJSContext *context1;
@property (nonatomic, weak) UIView * pageView1;
@property (nonatomic, strong) UIView *hmRootView2;
@property (nonatomic, weak) HMJSContext *context2;
@property (nonatomic, weak) UIView * pageView2;
@end
@implementation HMViewController
// ....
// 多 hummer 视图容器
- (void)initHMRootView {
/** hummer渲染view */
self.hmRootView1 = [[UIView alloc] initWithFrame:frame1];
[self.view addSubview:self.hmRootView1];
self.hmRootView2 = [[UIView alloc] initWithFrame:frame2];
[self.view addSubview:self.hmRootView2];
}
#pragma mark -渲染脚本
- (void)renderWithScript:(NSString *)script1 script2:(NSString *)script2{
// ....
// 执行第一个脚本
HMJSContext *context = [HMJSContext contextInRootView:self.hmRootView1];
[context evaluateScript:script1 fileName:self.URL];
self.pageView1 = self.hmRootView1.subviews.firstObject;
self.context1 = [[HMJSGlobal globalObject] currentContext:self.pageView1.hmContext];
// 执行第二个脚本
HMJSContext *context2 = [HMJSContext contextInRootView:self.hmRootView2];
[context2 evaluateScript:script2 fileName:self.URL];
self.pageView2 = self.hmRootView2.subviews.firstObject;
self.context2 = [[HMJSGlobal globalObject] currentContext:self.pageView2.hmContext];
}
// ...
@end
注意
无论是单容器单页面,还是单容器嵌套多 Hummer 页面,容器的生命周期还是一个。并且依赖 iOS 原生的生命周期机制。
三、实现自定义导出组件
iOS 同样使用注解的方式实现 js 和 native 的对应关系。 比如我们要实现一个 MyComponent 组件,其中包括如下:
属性
- style(实例属性)
- version(类属性)
方法
- show(实例方法)
- className(类方法)
- doSomething(方法带参数,返回值)
则示例代码如下:
Naive 示例
@interface ()
@property (nonatomic,strong) NSDictionary *nativeStyle;
@end
@implementation MyComponent
// 第一个参数为 js 方法名,第二个参数为原生的 sel,
HM_EXPORT_METHOD(show, __show)
- (void)__show{
//...
}
HM_EXPORT_CLASS_METHOD(className, __className)
+ (NSString *)__className{
return @"MyComponent";
}
HM_EXPORT_METHOD(doSomething, __doSomething:)
- (BOOL)__doSomething:(HMBaseValue *)value {
//需要 自己实现类型转换,可参考 JavascriptCore 的 JSValue 类型对应及转换。
NSString *expectType = value.toString;
//hummer 会根据返回值类型转换对应的js 类型。与 JSC 规则保持一致。
return YES;
}
HM_EXPORT_PROPERTY(style, __style, __setStyle:)
- (NSDictionary *)__style {
return self.nativeStyle;
}
- (void)__setStyle:(HMBaseValue *)value {
self.nativeStyle = value.toDictionary;
}
HM_EXPORT_CLASS_PROPERTY(version, __version, __setVersion:)
static NSString *version;
+ (NSString *)__version {
return version;
}
+ (void)__setVersion:(HMBaseValue *)value {
version = value.toString;
}
@end
JS 侧使用示例
let component = new MyComponent()
component.style = {key:value}
let result:bool = component.doSomething('param')
MyComponent.className()
let version = MyComponent.version;
其他说明
- 组件之间可以存在继承关系。例如想导出新的视图,则 iOS 侧需要继承 UIView。
- 方法、属性中的参数都为 HMBaseValue 类型,需要 iOS 侧根据约定调用对应类型转换api,类型转换 api 和 JSC 一致。
四、Bridge 用法
在通过 Bridge 通信之前,需要明确一个概念:所有 JS 侧的对象(类)都对应 Native 的 HMBaseValue。
Native 向 JS 注入方法
动态注入方法 API
@implementation HMBaseValue
- (void)setValue:(id)value forProperty:(NSString *)property;
@end
iOS 侧的方法注入类似 runtime 的动态添加机制:给对象添加方法则传入类,给类添加则传入类对象。
在此基础上,如果我们想要注入自定义的方法,则需要先获取对应的对象(类)。
1. Native 向 JS 静态类注册方法
// 向 MyComponent 注入 类方法:version。。
// 通过导出方法回去到 MyComponent 对应的 js 类对象。
HM_EXPORT_METHOD(setClass, __setClass:)
+ (void)__setClass:(HMBaseValue *)value {
HMFunctionType func = ^(NSArray *_Nullable argumentArray) {
return @"1.0";
};
// 获取到js 类对象,通过 setValue 注入 HMFunctionType 闭包。
[value setValue:func forProperty:@"version"];
}
INFO
js 中调用:
- MyComponent.setClass(MyComponent);
- MyComponent.version();
2. Native 向 JS 对象注册方法
跟上述类似,只是 HMBaseValue 为 js对象,而非实例。
Native 调用 JS 侧方法
Native 调用 JS 侧 API
- (nullable HMBaseValue *)callWithArguments:(nullable NSArray *)arguments;
根据上面一节 HMBaseValue 的对象定义,要调用 JS 侧的方法。需要如下两步:
- 获取对应对象或类。
- 使用 callWithArguments: api
1. Native 调用 JS 侧类方法
class MyComponent {
function version(){
}
}
同样 将 MyComponent 传递到 Native 获取到对应的 HMBaseValue 后使用:
[HMBaseValue callWithArguments:@[]]
2. Native 调用 JS 某个对象的方法
同上。
最后说明
每一个 js 值(对象或类),在 native 侧都使用 HMBaseValue 表示,因此,只要可以拿到 HMBaseValue, 则可以进行注入操作。