关于在iOS中AOP思想与组件化相结合的架构模式探索(AOP实现热更新)(二) 既然上一篇讲到了利用AOP思想可以动态的添加功能,那我们这篇就讨论下可否实现理论上的热更新。
我们同样利用Aspects 去实现
首先我们可以查看Aspects源码 1 2 3 4 5 6 7 typedef NS_OPTIONS(NSUInteger, AspectOptions) { AspectPositionAfter = 0, /// 在原始实现之后调用 (default) AspectPositionInstead = 1, /// 将替换原来的实现。 AspectPositionBefore = 2, /// 在原始实现之前调用。 AspectOptionAutomaticRemoval = 1 << 3 /// 将在第一次执行之后删除hook。 };
看了这个位移枚举的值,是不是眼前一亮,我们完全可以利用Aspects去hook相关的方法然后在插入我们自己的代码,如果我们自己的代码是通过动态下发的,那不就可以实现热更新了吗?
一、首先实现插入代码的方法 上一篇,我们利用Aspects中提供的+ (id)aspect_hookSelector(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error;去实现方法交换,分析这个方法:
1 2 3 4 5 6 7 8 //类名去掉用此方法 //参数1 selector 使我们要hook的方法名 //参数2 options 即上面分析的位移枚举 //参数3 block返回AspectInfo协议对象 + (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error;
那么我们需要传递这些参数去给Aspects进行hook:
1 2 3 4 5 6 7 8 9 10 + (void)hookWithClassName:(NSString *)className selector:(NSString *)selector options:(AspectOptions)options{ Class cla = NSClassFromString(className); SEL sel = NSSelectorFromString(selector); [cla aspect_hookSelector:sel withOptions:options usingBlock:^(id<AspectInfo>aspectInfo){ NSLog(@"我是Hook方法"); } error:nil]; }
然后我们在相应的位置调用此方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. [self.view addSubview:self.btn]; [self.btn addTarget:self action:@selector(btnClick) forControlEvents:(UIControlEventTouchUpInside)]; //调用hook方法 传递的options为方法前调用 [AspectsHook hookWithClassName:@"ViewController" selector:@"btnClick" options:AspectPositionBefore]; } - (void)btnClick{ NSLog(@"正常点击"); } - (UIButton *)btn{ if (!_btn) { _btn = [UIButton buttonWithType:UIButtonTypeCustom]; _btn.frame = CGRectMake(100, 100, 100, 100); [_btn setTitle:@"点击" forState:(UIControlStateNormal)]; [_btn setBackgroundColor:[UIColor blueColor]]; } return _btn; } @end
我们此时点击按钮看到的打印信息:
1 2 2018-09-06 18:00:45.805674+0800 Demo1Method[24569:1844918] 我是Hook方法 2018-09-06 18:00:45.805879+0800 Demo1Method[24569:1844918] 正常点击
那么,怎么实现热更新啊?
二、这里我们就需要熟练的掌握JSContext 这里要使用到的JavaScriptCore是iOS7之后新推出的API,JSContent要怎么使用啊?
JSContext : JSContext是一个JavaScript执行环境。所有 JavaScript执行发生在一个上下文中,所有JavaScript值 与上下文相关。
JSValue : JSValue是对JavaScript值的引用。每个JSValue 源自JSContext并持有对它的强引用。 当JSValue实例方法创建新JSValue时,新值 源自相同的JSContext。
具体使用方法我们在这里不展开讨论,这里贴上搜索链接:https://www.aliyun.com/jiaocheng/topic_38345.html
具体怎么用呢?我们先假设一段JS代码:
1 2 3 //具体我们hook了ViewController的btnClick方法, //传入的2指的是位移枚举的AspectPositionBefore, hookMethod("ViewController","btnClick",2);
首先注册JSContext方法:
1 2 3 4 JSContext * content = [[JSContext alloc] init]; content[@"hookMethod"] = ^(NSString *className, NSString *selectorName, AspectOptions options){ [self hookWithClassName:className selector:selectorName options:options]; };
我们通过下面方法获取JSValue,然后转化为object类型:
1 2 JSValue *jsValue = [[self context] evaluateScript:jsString]; id obj = jsValue.toObject;
最后我们去实现Ta:
1 2 3 4 [AspectsHook hook]; NSString *jsPath = [[NSBundle mainBundle] pathForResource:@"hookMethod" ofType:@"js"]; NSString *jsString = [NSString stringWithContentsOfFile:jsPath encoding:NSUTF8StringEncoding error:nil]; [AspectsHook getJSString:jsString];
然后运行点击按钮得到的事件:
1 2 2018-09-06 23:05:59.003088+0800 Demo1Method[26189:2045481] 我是Hook方法 2018-09-06 23:05:59.003253+0800 Demo1Method[26189:2045481] 正常点击
但此时我们的“我是Hook方法”还是写死到本地代码中的,那么我们怎么去执行js下发的实现代码呢? 通过JSValue的callWithArguments再次调起JSContext的响应方法。
1 2 3 4 5 6 7 8 9 10 11 + (void)hookWithClassName:(NSString *)className selector:(NSString *)selectorName options:(AspectOptions)options value:(JSValue *)value{ Class cla = NSClassFromString(className); SEL sel = NSSelectorFromString(selectorName); [cla aspect_hookSelector:sel withOptions:options usingBlock:^(id<AspectInfo>aspectInfo){ NSLog(@"我是Hook方法"); [value callWithArguments:@[aspectInfo.instance,aspectInfo.originalInvocation,aspectInfo.arguments]]; } error:nil]; }
那么我们引入代码的话,就得涉及到NSInvocation的使用,我们下节再讨论。