UIWebView 和 H5 交互的幾個(gè)坑點(diǎn)
當(dāng)我們建立了 JSContext 和 UIWebView 之間的關(guān)系之后。
很自然的一個(gè)場(chǎng)景就是我們會(huì)把 OC 的方法(block) 注入到 JS 中,讓 JS 來調(diào)用。
這里有個(gè)場(chǎng)景:在 HTML 中,有一個(gè)按鈕,點(diǎn)擊這個(gè)按鈕會(huì)modal 出來一個(gè)控制器。
然后在打開的控制器里,也有個(gè)一 WebView,里面有個(gè)按鈕,點(diǎn)擊之后,需要關(guān)閉當(dāng)前這個(gè)控制器。
效果圖:

從第一個(gè)控制器打開第二個(gè)控制器的OC方法注入
- (void)webViewDidFinishLoad:(UIWebView *)webView {
// 獲取 WebView 的 JS 執(zhí)行環(huán)境
_context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
_context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"獲取 UIWebView 的 JS 執(zhí)行環(huán)境失敗,原因: %@",exception.toString);
};
// 注入打開控制器的方法
// self -> _context -> block -> self
__weak typeof(self) weakSelf = self;
_context[@"openVC"] = ^{
NSLog(@"JS 調(diào)用 OC 方法的線程: %@",[NSThread currentThread]);
__strong typeof(weakSelf) sself = weakSelf;
UIViewController *vc = [[SecondController alloc] init];
vc.view.backgroundColor = [UIColor orangeColor];
/**
his application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.
*/
// UI 操作要放到主線程。
dispatch_async(dispatch_get_main_queue(), ^{
[sself presentViewController:vc animated:YES completion:nil];
});
};
NSLog(@"%@",@"OC 方法注入完成!");
}
上面那段代碼遇到的坑點(diǎn)::
一、要注意循環(huán)引用。
其中 self --> jsContext -> block -> self

解決方法也很簡(jiǎn)單:使用 weak - strong dance 打斷 block 對(duì) ViewController 的強(qiáng)引用。
二、OC 往 JS 中注入函數(shù),這個(gè)函數(shù)在調(diào)用的時(shí)候,是在子線程執(zhí)行的。
JS 調(diào)用 OC 方法的線程: <NSThread: 0x618000073000>{number = 5, name = (null)}
所以,如果 JS 在調(diào)用 OC 的方法涉及到 UI 界面的操作的話,要記得回到主線程。
// UI 操作要放到主線程。
dispatch_async(dispatch_get_main_queue(), ^{
[sself presentViewController:vc animated:YES completion:nil];
});
JSValue & JSContext & Block 之間的引用關(guān)系
三者都是強(qiáng)引用。
主要包括在:
- JSValue 引用 JSContext 里面的 JS 對(duì)象和變量。而 JS 對(duì)象和變量依賴于 JSContext。所以JSValue 對(duì) JSContext 是強(qiáng)引用。
- 我們往 JS 中注入 OC 函數(shù)的 block 時(shí)候,JSContext 會(huì)拉住這個(gè) OC 的 block。所以 JSContext 也會(huì)強(qiáng)引住這個(gè) block。
- 在 block 中訪問 JSValue 。等于是 block 捕獲了外部變量,這個(gè)本來沒什么。block 經(jīng)常性的會(huì)引用外部變量。但是由于,JSContext -> block , block -> JSValue , JSValue -> JSContext。 所以就產(chǎn)生了循環(huán)引用了。
- 在 block 中直接訪問 JSContext 也會(huì)產(chǎn)生強(qiáng)引用關(guān)系。道理也很簡(jiǎn)單:JSContext -> block ,block -> JSContext。
下面分別有代碼帶解決這些強(qiáng)引用問題。
1.在 block 中,直接訪問 JSValue 導(dǎo)致循環(huán)引用的問題。

方法一:將 JSValue 作為參數(shù)傳遞到 Block 中。
// JSValue -> JSContext
JSValue *value = [JSValue valueWithObject:@{@"name" : @"lisi",@"age" : @22} inContext:_context];
// JSContext -—> Block
_context[@"ocFunc"] = ^(JSValue *value) {
// Block -> JSValue
// 解決辦法,把 JSValue 當(dāng)做參數(shù)傳遞到 block 中。
NSLog(@"%@",value.toDictionary);
};
方法二:使用 weak - strong dance
// JSValue 被 block 強(qiáng)引用,解決方式二。
__weak typeof(value) weakValue = value;
_context[@"ocFunc3"] = ^ {
__strong typeof(weakValue) ssValue = weakValue;
NSLog(@"weak - strong dance JSValue:%@",ssValue.toDictionary);
};
2.在 block 中直接訪問 JSContext 的循環(huán)引用問題

解決方式一 : 使用 [JSContext currentContext] 來訪問 JSContext。
_context[@"ocFunc2"] = ^{
// 解決辦法1:使用 [JSContext currentContext];
[JSContext currentContext][@"doSomething"] = ^(NSString *something) {
NSLog(@"something");
};
}
解決方式二:使用 weak - strong dance 來解決
// JSContext -> block
__weak typeof(_context) weakContext = _context;
_context[@"ocFunc2"] = ^{
// 解決方法二,使用 weak-strong dance
__strong typeof(weakContext) ssContext = weakContext;
ssContext[@"doSomething2"] = ^(NSString *someThing) {
NSLog(@"%@",someThing);
};
};
最后總結(jié):
- 由于 JSValue 指向的 JS 值依賴于 JSContext 上下文環(huán)境。所以 JSValue 天生的強(qiáng)引用了 JSContext。
- JSContext 注入 OC 的方法到 JS。JS 需要調(diào)用這個(gè) OC 的 block。所以 JSContext 天生的會(huì)強(qiáng)引用這個(gè) block。
- 在 block 中直接用捕獲的方式使用 JSValue 導(dǎo)致循環(huán)引用是因?yàn)椋篔SValue 強(qiáng)引用了 JSContext。
- 在 block 中直接使用 JSContext,導(dǎo)致了循環(huán)引用就很普通了:兩個(gè)對(duì)象都相互引用了對(duì)方。