JavaScriptCore的巨坑(JSExportAs方式綁定的本地通信)

前言

本篇分享的類型不是學習教程,并且要有一點JavaScriptCore基礎。

畢竟這一塊網上一大堆的學習教程,博主就沒必要班門弄斧了。

本篇的目的是分享JavaScriptCore中用JSExport協議和JSExportAs宏來進行jsoc通信的兩個大坑。
<ol>
<li>內存泄露</li>
<li>調用-[JSValue callWithArguments]野指針問題</li>
</ol>

block方式來進行js和oc的通信沒這兩個大坑。

第一個坑:內存泄露

一般綁定JSContext里的native的寫法都是self.context[@"native"] = self。但是這樣寫會產生內存泄露(泄露原理就是互相持有了),這個坑隨便百度Google一下也能找到很多解決方案。目前博主的解決方案是native指定一個新的對象,然后在指定對象里實現JSExport協議。
貼上博主在項目里用到的核心代碼 :

和js通信的控制器頁面核心代碼

// 以 JSExport 協議關聯 native 的方法
self.context[@"native"] = [[NMFormFlowWapNativeManager alloc] initWithDelegate:self];

NMFormFlowWapNativeManager.h

@interface NMFormFlowWapNativeManager : NSObject

- (instancetype)initWithDelegate:(id<NMFormFlowWapNativeManagerDelegate>)delegate;

@property (nonatomic,weak) id<NMFormFlowWapNativeManagerDelegate> delegate;

@end

NMFormFlowWapNativeManager.m

@import JavaScriptCore;

@protocol TestJSExport <JSExport>

JSExportAs(nativeCall, - (void)nativeCallHandleWithType:(NSString *)nativeType parameter:(NSString *)parameter jsType:(NSString *)jstype);

@end

@interface NMFormFlowWapNativeManager () <TestJSExport>

@end

@implementation NMFormFlowWapNativeManager

- (instancetype)initWithDelegate:(id<NMFormFlowWapNativeManagerDelegate>)delegate {
    if (self = [super init]) {
        self.delegate = delegate;
    }
    return self;
}

- (void)nativeCallHandleWithType:(NSString *)nativeType parameter:(NSString *)parameter jsType:(NSString *)jsType {
    NSDictionary *dicParams = [NSJSONSerialization JSONObjectWithData:[parameter dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableLeaves error:nil];
    [self.delegate nativeCallHandleWithThread:webThread type:nativeType parameter:dicParams jsType:jsType];
}

PS:代碼并不是完整的,但最核心的關鍵已經貼上來了。順便簡單解釋一下。由于native管理的對象交給了另一個,所以在管理者對象里新開了一個代理回調。方便在控制器那邊接收得到JS的事件。只要有點基礎的,一看就懂了。畢竟本篇不是學習教程,而是分享坑的。

第二個坑:-[JSValue callWithArguments]野指針問題

這個問題有點奇葩,JSValue的callWithArguments就是oc調用js函數所執行的方法。那這簡單的函數怎么發生野指針問題尼。
那就是oc進行網絡請求,請求完回調的時候調用JSValue的callWithArguments的方法就是產生野指針,而且是間接性的,有時候會有時候不會。一旦崩潰基本都直接飛去main函數了。。。。

這個問題百度Google都找了許久也沒找到類似的問題和解決方案。只是崩潰的時候,左邊的堆棧提示webThread(當時猜測可能是線程間通信影響的此問題),然后我蒙一下切換到webView的線程里去調用callWithArguments函數試試,結果就從未發生過崩潰了。

例子:

假設,h5上有一個圖片顯示和一個button,點擊button的時候,調用本地攝像頭并且上傳圖片到服務器,上傳完之后在調用js一個函數,告訴js圖片上傳成功,讓js去做對應的邏輯。這個時候網絡請求完回調里的線程是主線程,調用callWithArguments的時候,就會間接性的崩潰。

解決方案

解決辦法就是回到webView的線程去調用callWithArguments就不會崩潰(因為js和oc綁定的函數,在函數里執行的代碼不是在主線程里執行的)。
模擬代碼:

///假設這個函數是和js的test函數綁定的。如果監聽到這個函數就進行網絡請求或者上傳圖片等操作。
- (void)test {
  //獲取webView線程,因為js和oc綁定的函數里執行的代碼不是在主線程里。
NSThread *webThread = [NSThread currentThread];
//網絡請求
@weakify(self);
[HTTPRequest requestGetTokenWithFinished:^(void){
  @strongify(self);

  //通知js請求完了。

 //正常情況下是直接在這里調用,但是會間接性發生野指針問題,差不多每隔四五次發生一次野指針。
  //JSValue *jsCall = self.context[@"jsCall"];
  //[jsCall callWithArguments:nil];

  //線程安全的,用此方式,筆者再也沒發生過野指針問題。
  [self performSelector:@selector(jsCall) onThread:webThread withObject:nil waitUntilDone:NO];
}];
}

- (void)jsCall {
  JSValue *jsCall = self.context[@"jsCall"];
  [jsCall callWithArguments:nil];
}

結語

總之筆者分享此文章的主要目的是第二個野指針問題,因為筆者在Google和stackoverflow里也找了很久也找不到問題原因,然后都是蒙對的,所以才來進行分享。可能對于不懂JavaScriptCore看起來有點困難,總之可以先了解一下。而對于js和oc的通信的業務不復雜的或者使用block進行通信的,應該很難遇到此問題。再者,網上很多學習教程基本都是推薦callWithArguments在主線程里調用,但目前筆者認為應該還是讓它在webView的線程里去執行(那個野指針問題就是在主線程里執行所發生的)。

callWithArguments野指針問題的底層實際發生原理也并不是很清楚。所以目前只能說博主是怎么解決的,但是為什么.....博主也不知其然了。有知道的方便的話也可告知一下。

而對于demo.....筆者也想寫,但對于html、js并不是很熟悉(頂多看得懂幾個標簽)。所以....無能為力奉上demo了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 本文由我們團隊的 糾結倫 童鞋撰寫。 寫在前面 本篇文章是對我一次組內分享的整理,大部分圖片都是直接從keynot...
    知識小集閱讀 15,286評論 11 172
  • 寫在前面 本篇文章是對我一次組內分享的整理,大部分圖片都是直接從keynote上截圖下來的,本來有很多炫酷動效的,...
    等開會閱讀 14,509評論 6 69
  • 注:本文copy自http://www.lxweimin.com/p/ac534f508fb0,純屬當筆記使用。 概...
    BookKeeping閱讀 742評論 1 3
  • 原創文章轉載請注明出處,謝謝 相信HotFix大家應該都很熟悉了,今天主要對于最近調研的一些方案做一些總結。iOS...
    北辰明閱讀 7,693評論 6 60
  • 只需要清理SDWebImage的圖片緩存,直接用SDImageCache單例的getSize方法 2.除了圖片還有...
    lym不解釋閱讀 196評論 0 0