当前位置:首页 > 游戏攻略 > 正文

ios热更新审核通关攻略(ios热更新解决方法)

一、Aspects为什么可以热更新

要达到修复,Native 层只要透出两种能力就基本可以了:

  1. 在任意方法前后注入代码、替换代码 的能力。

  2. 调用任意类/实例方法的能力。

第 2 点不难,只要把 [NSObject performSelector:...] 那一套通过 JSContext 暴露出来即可。难的是第 1 点。而Aspects是可以满足的,只要把它的几个方法通过 JSContext 暴露给 JS 就可以了。

Aspects 是可以通过 AppStore 的审核。

[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];

这篇文章参考了limboy的文章,现在网上的热更新也基本都是在他的基础上改来改去,我们这次讲的是limboy开源的代码,github地址:
https://github.com/lzyy/felix。

下面我们写段崩溃的代码:

ios热更新审核通关攻略(ios热更新解决方法)  第1张

崩溃

然后我们修复一下:

ios热更新审核通关攻略(ios热更新解决方法)  第2张

热更新修复崩溃问题

如果想修改一个ViewController里面的UItableView的代理方法(例如tableView: numberOfRowsInSection:),上面的字符串替换成:

fixInstanceMethodReplace("MyTableViewController", "tableView:numberOfRowsInSection:", function(instance, invocation){
// 这里就是新的实现
})

热更新过程:

首先通过网络请求再结合一些加密 获取下发的js 字符串。然后执行[Felix evalString:js字符串]方法 就可以了。

为了读源代码,我们先来温习一下JavaScriptCore。

如果对这块比较熟悉的话就可以跳过这一小节。

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:

 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

2、JS调用OC

 self.context = [[JSContext alloc] init];
 
 self.context[@"add"] = ^(NSInteger a, NSInteger b) {
 NSLog(@"---%@", @(a + b));
 };
 
 [self.context evaluateScript:@"add(2,3)"];

我们定义一个block,然后保存到context里面,其实就是转换成了JS的function。然后我们直接执行这个function,调用的就是我们的block里面的内容了。

实际中的简单例子:

OC调用JS的nativeCallJS方法。

JS调用OC的jsCallNative方法。

<html>
<body>
 <script type="text/javascript">
 var nativeCallJS = function(parameter) {
 alert (parameter);
 };
 </script>
 <button type="button" onclick = "jsCallNative('jsParameter')"/>调用OC代码</button>
</body>
</html>

OC中的代码:

- (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的github地址已经在上面给出了。

我们看上面热更新修复的代码,第一句是:

[Felix fixIt];

下面我们看下这个方法:

ios热更新审核通关攻略(ios热更新解决方法)  第3张


第一句:

 JSContext *tempContext = [self context];

先初始化了一个JSContext单例。

ios热更新审核通关攻略(ios热更新解决方法)  第4张


然后执行:

tempContext[@"fixInstanceMethod"] = ^(NSString *instanceName, NSString *selectorName, JSValue *fixImpl) {
 [self _fixWithMethod:NO aspectionOptions:AspectPositionInstead instanceName:instanceName selectorName:selectorName fixImpl:fixImpl];
 };

调用的fixInstanceMethod方法:

ios热更新审核通关攻略(ios热更新解决方法)  第5张


代码里,先根据传过来的instanceName 实例化一个对象(或者类)。然后根据传过来的方法名 初始化SEL,然后调用Aspects的aspect_hookSelector方法。传入的是AspectPositionInstead,表示替换之前的方法。

然后在usingBlock里回调方法:

[fixImpl callWithArguments:@[aspectInfo.instance, aspectInfo.originalInvocation, aspectInfo.arguments]];

回到刚开始我们解决崩溃问题的代码:

ios热更新审核通关攻略(ios热更新解决方法)  第2张


fixImpl对应的是:

function(instance, originInvocation, originArguments){ 
 if (originArguments[0] == 0) { 
 console.log('zero goes here'); 
 } else { 
 runInvocation(originInvocation); 
 } 
 });

这样就解决了问题。

上图中,最后执行JavaScriptCore的evaluateScript方法:

[Felix evalString:fixJsStr];
里面具体的实现:
+ (void)evalString:(NSString *)javascriptString
{
 [[self context] evaluateScript:javascriptString];
}


0