前言
本篇分享的類型不是學(xué)習(xí)教程,并且要有一點(diǎn)JavaScriptCore基礎(chǔ)。
畢竟這一塊網(wǎng)上一大堆的學(xué)習(xí)教程,博主就沒必要班門弄斧了。
本篇的目的是分享JavaScriptCore中用JSExport協(xié)議和JSExportAs宏來進(jìn)行js和oc通信的兩個(gè)大坑。
<ol>
<li>內(nèi)存泄露</li>
<li>調(diào)用-[JSValue callWithArguments]野指針問題</li>
</ol>
用
block方式來進(jìn)行js和oc的通信沒這兩個(gè)大坑。
第一個(gè)坑:內(nèi)存泄露
一般綁定JSContext里的native的寫法都是self.context[@"native"] = self。但是這樣寫會產(chǎn)生內(nèi)存泄露(泄露原理就是互相持有了),這個(gè)坑隨便百度Google一下也能找到很多解決方案。目前博主的解決方案是native指定一個(gè)新的對象,然后在指定對象里實(shí)現(xiàn)JSExport協(xié)議。
貼上博主在項(xiàng)目里用到的核心代碼 :
和js通信的控制器頁面核心代碼
// 以 JSExport 協(xié)議關(guān)聯(lián) 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:代碼并不是完整的,但最核心的關(guān)鍵已經(jīng)貼上來了。順便簡單解釋一下。由于
native管理的對象交給了另一個(gè),所以在管理者對象里新開了一個(gè)代理回調(diào)。方便在控制器那邊接收得到JS的事件。只要有點(diǎn)基礎(chǔ)的,一看就懂了。畢竟本篇不是學(xué)習(xí)教程,而是分享坑的。
第二個(gè)坑:-[JSValue callWithArguments]野指針問題
這個(gè)問題有點(diǎn)奇葩,JSValue的callWithArguments就是oc調(diào)用js函數(shù)所執(zhí)行的方法。那這簡單的函數(shù)怎么發(fā)生野指針問題尼。
那就是oc進(jìn)行網(wǎng)絡(luò)請求,請求完回調(diào)的時(shí)候調(diào)用JSValue的callWithArguments的方法就是產(chǎn)生野指針,而且是間接性的,有時(shí)候會有時(shí)候不會。一旦崩潰基本都直接飛去main函數(shù)了。。。。
這個(gè)問題百度Google都找了許久也沒找到類似的問題和解決方案。只是崩潰的時(shí)候,左邊的堆棧提示
webThread(當(dāng)時(shí)猜測可能是線程間通信影響的此問題),然后我蒙一下切換到webView的線程里去調(diào)用callWithArguments函數(shù)試試,結(jié)果就從未發(fā)生過崩潰了。
例子:
假設(shè),h5上有一個(gè)圖片顯示和一個(gè)button,點(diǎn)擊button的時(shí)候,調(diào)用本地?cái)z像頭并且上傳圖片到服務(wù)器,上傳完之后在調(diào)用js一個(gè)函數(shù),告訴js圖片上傳成功,讓js去做對應(yīng)的邏輯。這個(gè)時(shí)候網(wǎng)絡(luò)請求完回調(diào)里的線程是主線程,調(diào)用callWithArguments的時(shí)候,就會間接性的崩潰。
解決方案
解決辦法就是回到webView的線程去調(diào)用callWithArguments就不會崩潰(因?yàn)閖s和oc綁定的函數(shù),在函數(shù)里執(zhí)行的代碼不是在主線程里執(zhí)行的)。
模擬代碼:
///假設(shè)這個(gè)函數(shù)是和js的test函數(shù)綁定的。如果監(jiān)聽到這個(gè)函數(shù)就進(jìn)行網(wǎng)絡(luò)請求或者上傳圖片等操作。
- (void)test {
//獲取webView線程,因?yàn)閖s和oc綁定的函數(shù)里執(zhí)行的代碼不是在主線程里。
NSThread *webThread = [NSThread currentThread];
//網(wǎng)絡(luò)請求
@weakify(self);
[HTTPRequest requestGetTokenWithFinished:^(void){
@strongify(self);
//通知js請求完了。
//正常情況下是直接在這里調(diào)用,但是會間接性發(fā)生野指針問題,差不多每隔四五次發(fā)生一次野指針。
//JSValue *jsCall = self.context[@"jsCall"];
//[jsCall callWithArguments:nil];
//線程安全的,用此方式,筆者再也沒發(fā)生過野指針問題。
[self performSelector:@selector(jsCall) onThread:webThread withObject:nil waitUntilDone:NO];
}];
}
- (void)jsCall {
JSValue *jsCall = self.context[@"jsCall"];
[jsCall callWithArguments:nil];
}
結(jié)語
總之筆者分享此文章的主要目的是第二個(gè)野指針問題,因?yàn)楣P者在Google和stackoverflow里也找了很久也找不到問題原因,然后都是蒙對的,所以才來進(jìn)行分享??赡軐τ诓欢?code>JavaScriptCore看起來有點(diǎn)困難,總之可以先了解一下。而對于js和oc的通信的業(yè)務(wù)不復(fù)雜的或者使用block進(jìn)行通信的,應(yīng)該很難遇到此問題。再者,網(wǎng)上很多學(xué)習(xí)教程基本都是推薦callWithArguments在主線程里調(diào)用,但目前筆者認(rèn)為應(yīng)該還是讓它在webView的線程里去執(zhí)行(那個(gè)野指針問題就是在主線程里執(zhí)行所發(fā)生的)。
而callWithArguments野指針問題的底層實(shí)際發(fā)生原理也并不是很清楚。所以目前只能說博主是怎么解決的,但是為什么.....博主也不知其然了。有知道的方便的話也可告知一下。
而對于demo.....筆者也想寫,但對于html、js并不是很熟悉(頂多看得懂幾個(gè)標(biāo)簽)。所以....無能為力奉上demo了。