JavaScriptCore簡介
- JavaScriptCore中主要的類
- JSContext
- JSValue
- JSExport
- JSManagedValue
- JSVirtualMachine
- Native Code 和 JS 之間的互相調(diào)用
- Native Code 與UIWebView中的JS交互
- Native Code 與JS文件直接交互
JavaScriptCore背景
iOS中的JavaScriptCore.framework其實(shí)只是基于webkit(Safari的瀏覽器引擎)中 以C/C++實(shí)現(xiàn)的JavaScriptCore的一個(gè)包裝,在iOS7中,Apple將其作為一個(gè)標(biāo)準(zhǔn)庫供開發(fā)者使用
JavaScriptCore主要功能
- JavaScriptCore主要是對JS進(jìn)行解析和提供執(zhí)行環(huán)境。
- JavaScriptCore可以讓我們脫離webview直接運(yùn)行我們的js
- JavaScriptCore提供一種動(dòng)態(tài)局部升級和更新的邏輯优质,大大提高應(yīng)用的可擴(kuò)展性
- 對手機(jī)內(nèi)嵌web模式的新嘗試點(diǎn)圣絮,即通過Native+JS file的方式取代webview的方式
JavaScriptCore中主要的類
1.JSContext --- 在OC中創(chuàng)建JavaScript運(yùn)行的上下文環(huán)境
- (instancetype)init; // 創(chuàng)建JSContext對象质和,獲得JavaScript運(yùn)行的上下文環(huán)境
// 在特定的對象空間上創(chuàng)建JSContext對象凹蜈,獲得JavaScript運(yùn)行的上下文環(huán)境
- (instancetype)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;
// 運(yùn)行一段js代碼,輸出結(jié)果為JSValue類型
- (JSValue *)evaluateScript:(NSString *)script;
// iOS 8.0以后可以調(diào)用此方法
- (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL *)sourceURL NS_AVAILABLE(10_10, 8_0);
// 獲取當(dāng)前正在運(yùn)行的JavaScript上下文環(huán)境
+ (JSContext *)currentContext;
// 返回結(jié)果當(dāng)前執(zhí)行的js函數(shù) function () { [native code] } 臼疫,iOS 8.0以后可以調(diào)用此方法
+ (JSValue *)currentCallee NS_AVAILABLE(10_10, 8_0);
// 返回結(jié)果當(dāng)前方法的調(diào)用者[object Window]
+ (JSValue *)currentThis;
// 返回結(jié)果為當(dāng)前被調(diào)用方法的參數(shù)
+ (NSArray *)currentArguments;
// js的全局變量 [object Window]
@property (readonly, strong) JSValue *globalObject;
2.JSValue --- JavaScript中的變量和方法,可以轉(zhuǎn)成OC數(shù)據(jù)類型,每個(gè)JSValue都和JSContext相關(guān)聯(lián)并且強(qiáng)引用context
@textblock
Objective-C type | JavaScript type
--------------------+---------------------
nil | undefined
NSNull | null
NSString | string
NSNumber | number, boolean
NSDictionary | Object object
NSArray | Array object
NSDate | Date object
NSBlock (1) | Function object (1)
id (2) | Wrapper object (2)
Class (3) | Constructor object (3)
@/textblock
// 在context創(chuàng)建BOOL的JS變量
+ (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context;
// 將JS變量轉(zhuǎn)換成OC中的BOOL類型
- (BOOL)toBool;
// 修改JS對象的屬性的值
- (void)setValue:(id)value forProperty:(NSString *)property;
// JS中是否有這個(gè)對象
@property (readonly) BOOL isUndefined;
// 比較兩個(gè)JS對象是否相等
- (BOOL)isEqualToObject:(id)value;
// 調(diào)用者JSValue對象為JS中的方法名稱扣孟,arguments為參數(shù)烫堤,調(diào)用JS中Window直接調(diào)用的方法
- (JSValue *)callWithArguments:(NSArray *)arguments;
// 調(diào)用者JSValue對象為JS中的全局對象名稱,method為全局對象的方法名稱凤价,arguments為參數(shù)
- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;
// JS中的結(jié)構(gòu)體類型轉(zhuǎn)換為OC
+ (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context;
- JSExport --- JS調(diào)用OC中的方法和屬性寫在繼承自JSExport的協(xié)議當(dāng)中鸽斟,OC對象實(shí)現(xiàn)自定義的協(xié)議
// textFunction -- JS方法
// - (void) ocTestFunction:(NSNumber *)value sec:(NSNumber *)number -- OC方法
JSExportAs (textFunction,- (void) ocTestFunction:(NSNumber *)value sec:(NSNumber *)number);
4.JSManagedValue --- JS和OC對象的內(nèi)存管理輔助對象,主要用來保存JSValue對象,解決OC對象中存儲(chǔ)js的值,導(dǎo)致的循環(huán)引用問題
JSManagedValue *_jsManagedValue = [JSManagedValue managedValueWithValue:jsValue];
[_context.virtualMachine addManagedReference:_jsManagedValue];
- JSManagedValue本身只弱引用js值利诺,需要調(diào)用JSVirtualMachine的addManagedReference:withOwner:把它添加到JSVirtualMachine中富蓄,這樣如果JavaScript能夠找到該JSValue的Objective-C owner,該JSValue的引用就不會(huì)被釋放立轧。
5.JSVirtualMachine --- JS運(yùn)行的虛擬機(jī)格粪,有獨(dú)立的堆空間和垃圾回收機(jī)制,運(yùn)行在不同虛擬機(jī)環(huán)境的JSContext可以通過此類通信
Native Code 和 JS 之間的互相調(diào)用(以UIWebView中的JS為例)
OC調(diào)用JS代碼
js代碼
function showAlert(text)
{
alert(text);
}
1 .通過webView直接調(diào)用
[webView stringByEvaluatingJavaScriptFromString:@"showAlert('hahaha')"];
2.通過jsvalue
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
JSValue *inputValue = context[@"showAlert"];
[inputValue callWithArguments:@[@"hahaha"]];
}
JS調(diào)用OC代碼
<button onclick="showAlert('haha')">showAlert</button>
1.通過JSContext
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//JS調(diào)用OC的回調(diào)方法氛改,是在子線程帐萎,所以需要更新OC中的UI的話,需要切換到主線程
context[@"showAlert"] = ^(NSString *str){
NSLog(@"%@ %@",str,[NSThread currentThread]);
};
}
2.通過JSExport
<button onclick="native.myLog('123456');">調(diào)用OC中myLog方法</button>
自定義協(xié)議遵循JSExport協(xié)議 通過JSExportAS宏把mylog(JS的方法名)和myOCLog(OC發(fā)放)關(guān)聯(lián)起來
@protocol WebExport <JSExport>
JSExportAs(myLog ,- (void)myOCLog :(NSString *)string);
@end
@interface UIWebViewViewController () <UIWebViewDelegate,WebExport>
@end
指定context的native 為self(遵守了上面協(xié)議的對象)
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
context[@"native"] = self;
}
//JS調(diào)用OC的回調(diào)方法胜卤,是在子線程疆导,所以需要更新OC中的UI的話,需要切換到主線程
- (void)myOCLog :(NSString *)string
{
NSLog(@"%@",string);
NSLog(@"%s %@",__func__,[NSThread currentThread]);
}
然后 js代碼: native.myLog('123456')
就相當(dāng)于調(diào)用OC代碼: [self myOCLog:@"123456"];
這里需要主要 self強(qiáng)引用了context對象 而context[@"native"]也強(qiáng)引用了self 這里造成了循環(huán)引用葛躏。一般的解決方法是 把這里的self替換成一個(gè)中間對象來處理
JavaScriptCore和UIWebView的使用的注意事項(xiàng)
1.OC在與UIWebView中的JS交互的邏輯是澈段,先獲取UIWebView中的JS的執(zhí)行環(huán)境
self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
2.獲取UIWebView中的JS的執(zhí)行環(huán)境的時(shí)機(jī),一般在webViewDidFinishLoad時(shí)獲取,獲取不到的情況下舰攒,需改在其他方法中獲取
shouldStartLoadWithRequest:Sent before a web view begins loading a frame
webViewDidStartLoad:Sent after a web view starts loading a frame.
webViewDidFinishLoad:Sent after a web view finishes loading a frame
3.線程問題
1.JavaScriptCore中提供的API都是線程安全的败富,一個(gè)JSVirtualMachine在一個(gè)線程中,它可以包含多個(gè)JSContext摩窃,而且相互之間可以傳值兽叮,為了確保線程安全芬骄,這些context在運(yùn)行的時(shí)候會(huì)采用鎖,可以認(rèn)為是串行執(zhí)行
2.JS調(diào)用OC的回調(diào)方法鹦聪,是在子線程账阻,所以需要更新OC中的UI的話,需要切換到主線程
4.內(nèi)存問題
oc中使用ARC方式管理內(nèi)存(基于引用計(jì)數(shù))泽本,但JavaScriptCore中使用的是垃圾回收方式淘太,其中所有的引用都是強(qiáng)引用,但是我們不必?fù)?dān)心其循環(huán)引用规丽,js的垃圾回收能夠打破這些強(qiáng)引用蒲牧,有些情況需要考慮如下
- js調(diào)起OC回調(diào)的block中獲取JSConetxt容易循環(huán)引用
self.jsContext[@"jsCallNative"] = ^(NSString *paramer){
// 會(huì)引起循環(huán)引用
JSValue *value1 = [JSValue valueWithNewObjectInContext:
self.jsContext];
// 不會(huì)引起循環(huán)引用
JSValue *value = [JSValue valueWithNewObjectInContext:
[JSContext currentContext]];
};
JavaScriptCore中所有的引用都是強(qiáng)引用,所以在OC中需要存儲(chǔ)JS中的值的時(shí)候,需要注意
- 在oc中為了打破循環(huán)引用我們采用weak的方式嘁捷,不過在JavaScriptCore中我們采用內(nèi)存管理輔助對象JSManagedValue的方式造成,它能幫助引用計(jì)數(shù)和垃圾回收這兩種內(nèi)存管理機(jī)制之間進(jìn)行正確的轉(zhuǎn)換
JavaScriptCore單獨(dú)使用
js代碼如下:
globalObject = new Object();
globalObject.name = 100;
globalObject.nativeCallJS = function (parameter) {
alert (parameter);
};
OC讀取JS文件,并相互通信
NSString *jsPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"js"];
NSString *jsContent = [[NSString alloc] initWithContentsOfFile:jsPath encoding:NSUTF8StringEncoding error:nil];
JSContext *jsContext = [[JSContext alloc] init];
//捕獲運(yùn)行js腳本的錯(cuò)誤信息
jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
context.exception = exceptionValue;
NSLog(@"異常信息:%@", exceptionValue);
};
//js腳本添加到當(dāng)前的js執(zhí)行環(huán)境中
[jsContext evaluateScript:jsContent];
self.jsManager = [[JSManager alloc] init];
jsContext[@"globalObject"] = self.jsManager;
...
...