UIWebView 和 H5 交互的幾個(gè)坑點(diǎn)

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

循環(huán)引用關(guān)系

解決方法也很簡(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)引用。

主要包括在:

  1. JSValue 引用 JSContext 里面的 JS 對(duì)象和變量。而 JS 對(duì)象和變量依賴于 JSContext。所以JSValue 對(duì) JSContext 是強(qiáng)引用。
  2. 我們往 JS 中注入 OC 函數(shù)的 block 時(shí)候,JSContext 會(huì)拉住這個(gè) OC 的 block。所以 JSContext 也會(huì)強(qiáng)引住這個(gè) block。
  3. 在 block 中訪問 JSValue 。等于是 block 捕獲了外部變量,這個(gè)本來沒什么。block 經(jīng)常性的會(huì)引用外部變量。但是由于,JSContext -> block , block -> JSValue , JSValue -> JSContext。 所以就產(chǎn)生了循環(huán)引用了。
  4. 在 block 中直接訪問 JSContext 也會(huì)產(chǎn)生強(qiáng)引用關(guān)系。道理也很簡(jiǎn)單:JSContext -> block ,block -> JSContext。

下面分別有代碼帶解決這些強(qiáng)引用問題。

1.在 block 中,直接訪問 JSValue 導(dǎo)致循環(huán)引用的問題。

在 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)引用問題

在 block 中直接訪問 JSContext 導(dǎo)致的循環(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é):

  1. 由于 JSValue 指向的 JS 值依賴于 JSContext 上下文環(huán)境。所以 JSValue 天生的強(qiáng)引用了 JSContext。
  2. JSContext 注入 OC 的方法到 JS。JS 需要調(diào)用這個(gè) OC 的 block。所以 JSContext 天生的會(huì)強(qiáng)引用這個(gè) block。
  3. 在 block 中直接用捕獲的方式使用 JSValue 導(dǎo)致循環(huán)引用是因?yàn)椋篔SValue 強(qiáng)引用了 JSContext。
  4. 在 block 中直接使用 JSContext,導(dǎo)致了循環(huán)引用就很普通了:兩個(gè)對(duì)象都相互引用了對(duì)方。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容