JavaScriptCore詳解

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主要功能

  1. JavaScriptCore主要是對JS進(jìn)行解析和提供執(zhí)行環(huán)境。
  2. JavaScriptCore可以讓我們脫離webview直接運(yùn)行我們的js
  3. JavaScriptCore提供一種動(dòng)態(tài)局部升級和更新的邏輯优质,大大提高應(yīng)用的可擴(kuò)展性
  4. 對手機(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;

  1. 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;
   ...
   ...

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末雄嚣,一起剝皮案震驚了整個(gè)濱河市晒屎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缓升,老刑警劉巖鼓鲁,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異港谊,居然都是意外死亡骇吭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門歧寺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來燥狰,“玉大人,你說我怎么就攤上這事斜筐×拢” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵顷链,是天一觀的道長目代。 經(jīng)常有香客問我,道長嗤练,這世上最難降的妖魔是什么榛了? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮煞抬,結(jié)果婚禮上霜大,老公的妹妹穿的比我還像新娘。我一直安慰自己革答,他們只是感情好战坤,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布遮婶。 她就那樣靜靜地躺著,像睡著了一般湖笨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹦骑,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天慈省,我揣著相機(jī)與錄音,去河邊找鬼眠菇。 笑死边败,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的捎废。 我是一名探鬼主播绳姨,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼璧函,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起晃跺,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咆贬,沒想到半個(gè)月后肄满,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡智政,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年认罩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片续捂。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡垦垂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出牙瓢,到底是詐尸還是另有隱情劫拗,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布一罩,位于F島的核電站杨幼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏聂渊。R本人自食惡果不足惜差购,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望汉嗽。 院中可真熱鬧欲逃,春花似錦、人聲如沸饼暑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至彰居,卻和暖如春诚纸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背陈惰。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工畦徘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抬闯。 一個(gè)月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓井辆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親溶握。 傳聞我的和親對象是個(gè)殘疾皇子杯缺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容