继续上篇文章,iOS开发中热修复很重要,这篇讲如何用Aspects进行热修复。 要达到修复,Native 层只要透出两种能力就基本可以了: 第 2 点不难,只要把 [NSObject performSelector:...] 那一套通过 JSContext 暴露出来即可。难的是第 1 点。而Aspects是可以满足的,只要把它的几个方法通过 JSContext 暴露给 JS 就可以了。 Aspects 是可以通过 AppStore 的审核。 这篇文章参考了limboy的文章,现在网上的热更新也基本都是在他的基础上改来改去,我们这次讲的是limboy开源的代码,github地址:https://github.com/lzyy/felix。 下面我们写段崩溃的代码: 然后我们修复一下: 如果想修改一个ViewController里面的UItableView的代理方法(例如tableView: numberOfRowsInSection:),上面的字符串替换成: 热更新过程: 首先通过网络请求再结合一些加密 获取下发的js 字符串。然后执行[Felix evalString:js字符串]方法 就可以了。 为了读源代码,我们先来温习一下JavaScriptCore。 如果对这块比较熟悉的话就可以跳过这一小节。 JavaScriptCore是webkit的一个重要组成部分,主要是对JS进行解析和提供执行环境。 我们可以脱离webview直接运行我们的js。iOS7以前我们对JS的操作只有webview里面一个函数 stringByEvaluatingJavaScriptFromString,JS对OC的回调都是基于URL的拦截进行的操作。 JSContext是JS执行的环境。一个 Context 就是一个 JavaScript 代码执行的环境,也叫作用域。 JSValue:我们对JS的操作都是通过它。每个JSValue都是强引用一个context。OC和JS对象之间的转换也是通过它。 OC和JS之间的通信 1、OC中执行JS: 2、JS调用OC 我们定义一个block,然后保存到context里面,其实就是转换成了JS的function。然后我们直接执行这个function,调用的就是我们的block里面的内容了。 实际中的简单例子: OC调用JS的nativeCallJS方法。 JS调用OC的jsCallNative方法。 OC中的代码: felix的github地址已经在上面给出了。 我们看上面热更新修复的代码,第一句是: 下面我们看下这个方法: 第一句: 先初始化了一个JSContext单例。 然后执行: 调用的fixInstanceMethod方法: 代码里,先根据传过来的instanceName 实例化一个对象(或者类)。然后根据传过来的方法名 初始化SEL,然后调用Aspects的aspect_hookSelector方法。传入的是AspectPositionInstead,表示替换之前的方法。 然后在usingBlock里回调方法: 回到刚开始我们解决崩溃问题的代码: fixImpl对应的是: 这样就解决了问题。 上图中,最后执行JavaScriptCore的evaluateScript方法:一、Aspects为什么可以热更新
[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
NSLog(@"View Controller %@ will appear animated: %tu", aspectInfo.instance, animated);
} error:NULL];
fixInstanceMethodReplace("MyTableViewController", "tableView:numberOfRowsInSection:", function(instance, invocation){
// 这里就是新的实现
})
JavaScriptCore
self.context = [[JSContext alloc] init];
NSString *js = @"function add(a,b) {return a+b}";
[self.context evaluateScript:js];
JSValue *n = [self.context[@"add"] callWithArguments:@[@2, @3]];
NSLog(@"---%@", @([n toInt32]));//---5
self.context = [[JSContext alloc] init];
self.context[@"add"] = ^(NSInteger a, NSInteger b) {
NSLog(@"---%@", @(a + b));
};
[self.context evaluateScript:@"add(2,3)"];
<html>
<body>
<script type="text/javascript">
var nativeCallJS = function(parameter) {
alert (parameter);
};
</script>
<button type="button" onclick = "jsCallNative('jsParameter')"/>调用OC代码</button>
</body>
</html>
- (void)doSomeJsThings{
self.jsContext = [_webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"出现异常,异常信息:%@",exception);
};
//oc调用js
JSValue * nativeCallJS = self.jsContext[@"nativeCallJS"];
[nativeCallJS callWithArguments:@[@"hello word"]];//调用了js中方法"nativeCallJS",并且传参数@"hello word"
//在本地生成js方法,供js调用
self.jsContext[@"jsCallNative"] = ^(NSString *paramer){
JSValue *currentThis = [JSContext currentThis];
JSValue *currentCallee = [JSContext currentCallee];
NSArray *currentParamers = [JSContext currentArguments];
dispatch_async(dispatch_get_main_queue(), ^{
// js调起OC代码,代码在子线程,更新OC中的UI,需要回到主线程
NSLog(@"js传过来:%@",paramer);
});
NSLog(@"JS paramer is %@",paramer);
NSLog(@"currentThis is %@",[currentThis toString]);
NSLog(@"currentCallee is %@",[currentCallee toString]);
NSLog(@"currentParamers is %@",currentParamers);
};//生成native的js方法,方法名:@"jsCallNative",js可直接调用此方法
}
三、分析felix原理
[Felix fixIt];
JSContext *tempContext = [self context];
tempContext[@"fixInstanceMethod"] = ^(NSString *instanceName, NSString *selectorName, JSValue *fixImpl) {
[self _fixWithMethod:NO aspectionOptions:AspectPositionInstead instanceName:instanceName selectorName:selectorName fixImpl:fixImpl];
};
[fixImpl callWithArguments:@[aspectInfo.instance, aspectInfo.originalInvocation, aspectInfo.arguments]];
function(instance, originInvocation, originArguments){
if (originArguments[0] == 0) {
console.log('zero goes here');
} else {
runInvocation(originInvocation);
}
});
[Felix evalString:fixJsStr];
里面具体的实现:
+ (void)evalString:(NSString *)javascriptString
{
[[self context] evaluateScript:javascriptString];
}