iOS熱修復方案 系列二 、Aspects如何用來熱更新

繼續上篇文章,iOS開發中熱修復很重要,這篇講如何用Aspects進行熱修復。

一、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];
/<aspectinfo>

這篇文章參考了limboy的文章,現在網上的熱更新也基本都是在他的基礎上改來改去,我們這次講的是limboy開源的代碼,github地址:https://github.com/lzyy/felix。

下面我們寫段崩潰的代碼:

iOS熱修復方案 系列二 、Aspects如何用來熱更新

崩潰

然後我們修復一下:

iOS熱修復方案 系列二 、Aspects如何用來熱更新

熱更新修復崩潰問題

如果想修改一個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方法。

 


<button>調用OC代碼


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熱修復方案 系列二 、Aspects如何用來熱更新

第一句:

 JSContext *tempContext = [self context];

先初始化了一個JSContext單例。

iOS熱修復方案 系列二 、Aspects如何用來熱更新

然後執行:

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

調用的fixInstanceMethod方法:

iOS熱修復方案 系列二 、Aspects如何用來熱更新

代碼裡,先根據傳過來的instanceName 實例化一個對象(或者類)。然後根據傳過來的方法名 初始化SEL,然後調用Aspects的aspect_hookSelector方法。傳入的是AspectPositionInstead,表示替換之前的方法。

然後在usingBlock裡回調方法:

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

回到剛開始我們解決崩潰問題的代碼:

iOS熱修復方案 系列二 、Aspects如何用來熱更新

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];
}
iOS熱修復方案 系列二 、Aspects如何用來熱更新


分享到:


相關文章: