本文由雲+社區發表做者:殷源,專一移動客戶端開發,微軟Imagine Cup中國區特等獎得到者javascript
JavaScript愈來愈多地出如今咱們客戶端開發的視野中,從ReactNative到JSpatch,JavaScript與客戶端相結合的技術開始變得魅力無窮。本文主要講解iOS中的JavaScriptCore框架,正是它爲iOS提供了執行JavaScript代碼的能力。將來的技術突飛猛進,JavaScript與iOS正在碰撞出新的激情。java
JavaScriptCore是JavaScript的虛擬機,爲JavaScript的執行提供底層資源。程序員
在討論JavaScriptCore以前,咱們首先必須對JavaScript有所瞭解。web
《雷鋒和雷峯塔》chrome
Java 和 JavaScript 是兩門不一樣的編程語言 通常認爲,當時 Netscape 之因此將 LiveScript 命名爲 JavaScript,是由於 Java 是當時最流行的編程語言,帶有 「Java」 的名字有助於這門新生語言的傳播。編程
https://upload.wikimedia.org/...api
如今使用WebKit的主要兩個瀏覽器Sfari和Chromium(Chorme的開源項目)。WebKit起源於KDE的開源項目Konqueror的分支,由蘋果公司用於Sfari瀏覽器。其一條分支發展成爲Chorme的內核,2013年Google在此基礎上開發了新的Blink內核。數組
webkit是sfari、chrome等瀏覽器的排版引擎,各部分架構圖以下瀏覽器
JavaScriptCore主要由如下模塊組成:安全
關於更多JavaScriptCore的實現細節,參考 https://trac.webkit.org/wiki/...
JavaScriptCore是一個C++實現的開源項目。使用Apple提供的JavaScriptCore框架,你能夠在Objective-C或者基於C的程序中執行Javascript代碼,也能夠向JavaScript環境中插入一些自定義的對象。JavaScriptCore從iOS 7.0以後能夠直接使用。
在JavaScriptCore.h中,咱們能夠看到這個
#ifndef JavaScriptCore_h #define JavaScriptCore_h #include <JavaScriptCore/JavaScript.h> #include <JavaScriptCore/JSStringRefCF.h> #if defined(__OBJC__) && JSC_OBJC_API_ENABLED #import "JSContext.h" #import "JSValue.h" #import "JSManagedValue.h" #import "JSVirtualMachine.h" #import "JSExport.h" #endif #endif /* JavaScriptCore_h */
這裏已經很清晰地列出了JavaScriptCore的主要幾個類:
接下來咱們會依次講解這幾個類的用法。
這段代碼展現瞭如何在Objective-C中執行一段JavaScript代碼,而且獲取返回值並轉換成OC數據打印
//建立虛擬機 JSVirtualMachine *vm = [[JSVirtualMachine alloc] init]; //建立上下文 JSContext *context = [[JSContext alloc] initWithVirtualMachine:vm]; //執行JavaScript代碼並獲取返回值 JSValue *value = [context evaluateScript:@"1+2*3"]; //轉換成OC數據並打印 NSLog(@"value = %d", [value toInt32]); Output value = 7
一個JSVirtualMachine的實例就是一個完整獨立的JavaScript的執行環境,爲JavaScript的執行提供底層資源。
這個類主要用來作兩件事情:
看下頭文件SVirtualMachine.h
裏有什麼:
NS_CLASS_AVAILABLE(10_9, 7_0) @interface JSVirtualMachine : NSObject /* 建立一個新的徹底獨立的虛擬機 */ (instancetype)init; /* 對橋接對象進行內存管理 */ - (void)addManagedReference:(id)object withOwner:(id)owner; /* 取消對橋接對象的內存管理 */ - (void)removeManagedReference:(id)object withOwner:(id)owner; @end
每個JavaScript上下文(JSContext對象)都歸屬於一個虛擬機(JSVirtualMachine)。每一個虛擬機能夠包含多個不一樣的上下文,並容許在這些不一樣的上下文之間傳值(JSValue對象)。
然而,每一個虛擬機都是完整且獨立的,有其獨立的堆空間和垃圾回收器(garbage collector ),GC沒法處理別的虛擬機堆中的對象,所以你不能把一個虛擬機中建立的值傳給另外一個虛擬機。
線程和JavaScript的併發執行
JavaScriptCore API都是線程安全的。你能夠在任意線程建立JSValue或者執行JS代碼,然而,全部其餘想要使用該虛擬機的線程都要等待。
經過下面這個demo來理解一下這個併發機制
JSContext *context = [[CustomJSContext alloc] init]; JSContext *context1 = [[CustomJSContext alloc] init]; JSContext *context2 = [[CustomJSContext alloc] initWithVirtualMachine:[context virtualMachine]]; NSLog(@"start"); dispatch_async(queue, ^{ while (true) { sleep(1); [context evaluateScript:@"log('tick')"]; } }); dispatch_async(queue1, ^{ while (true) { sleep(1); [context1 evaluateScript:@"log('tick_1')"]; } }); dispatch_async(queue2, ^{ while (true) { sleep(1); [context2 evaluateScript:@"log('tick_2')"]; } }); [context evaluateScript:@"sleep(5)"]; NSLog(@"end");
context和context2屬於同一個虛擬機。
context1屬於另外一個虛擬機。
三個線程分別異步執行每秒1次的js log,首先會休眠1秒。
在context上執行一個休眠5秒的JS函數。
首先執行的應該是休眠5秒的JS函數,在此期間,context所處的虛擬機上的其餘調用都會處於等待狀態,所以tick和tick_2
在前5秒都不會有執行。
而context1所處的虛擬機仍然能夠正常執行tick_1
。
休眠5秒結束後,tick和tick_2
纔會開始執行(不保證前後順序)。
實際運行輸出的log是:
start tick_1 tick_1 tick_1 tick_1 end tick tick_2
一個JSContext對象表明一個JavaScript執行環境。在native代碼中,使用JSContext去執行JS代碼,訪問JS中定義或者計算的值,並使JavaScript能夠訪問native的對象、方法、函數。
top-level
的JS代碼,並可向global對象添加函數和對象定義API Reference
NS_CLASS_AVAILABLE(10_9, 7_0) @interface JSContext : NSObject /* 建立一個JSContext,同時會建立一個新的JSVirtualMachine */ (instancetype)init; /* 在指定虛擬機上建立一個JSContext */ (instancetype)initWithVirtualMachine: (JSVirtualMachine*)virtualMachine; /* 執行一段JS代碼,返回最後生成的一個值 */ (JSValue *)evaluateScript:(NSString *)script; /* 執行一段JS代碼,並將sourceURL認做其源碼URL(僅做標記用) */ - (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL*)sourceURL NS_AVAILABLE(10_10, 8_0); /* 獲取當前執行的JavaScript代碼的context */ + (JSContext *)currentContext; /* 獲取當前執行的JavaScript function*/ + (JSValue *)currentCallee NS_AVAILABLE(10_10, 8_0); /* 獲取當前執行的JavaScript代碼的this */ + (JSValue *)currentThis; /* Returns the arguments to the current native callback from JavaScript code.*/ + (NSArray *)currentArguments; /* 獲取當前context的全局對象。WebKit中的context返回的即是WindowProxy對象*/ @property (readonly, strong) JSValue *globalObject; @property (strong) JSValue *exception; @property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception); @property (readonly, strong) JSVirtualMachine *virtualMachine; @property (copy) NSString *name NS_AVAILABLE(10_10, 8_0); @end
一個JSContext對象對應了一個全局對象(global object)。例如web瀏覽器中中的JSContext,其全局對象就是window對象。在其餘環境中,全局對象也承擔了相似的角色,用來區分不一樣的JavaScript context的做用域。全局變量是全局對象的屬性,能夠經過JSValue對象或者context下標的方式來訪問。
一言不合上代碼:
JSValue *value = [context evaluateScript:@"var a = 1+2*3;"]; NSLog(@"a = %@", [context objectForKeyedSubscript:@"a"]); NSLog(@"a = %@", [context.globalObject objectForKeyedSubscript:@"a"]); NSLog(@"a = %@", context[@"a"]); / Output: a = 7 a = 7 a = 7
這裏列出了三種訪問JavaScript對象的方法
設置屬性也是對應的。
API Reference
/* 爲JSContext提供下標訪問元素的方式 */ @interface JSContext (SubscriptSupport) /* 首先將key轉爲JSValue對象,而後使用這個值在JavaScript context的全局對象中查找這個名字的屬性並返回 */ (JSValue *)objectForKeyedSubscript:(id)key; /* 首先將key轉爲JSValue對象,而後用這個值在JavaScript context的全局對象中設置這個屬性。 可以使用這個方法將native中的對象或者方法橋接給JavaScript調用 */ (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying>*)key; @end /* 例如:如下代碼在JavaScript中建立了一個實現是Objective-C block的function */ context[@"makeNSColor"] = ^(NSDictionary *rgb){ float r = [rgb[@"red"] floatValue]; float g = [rgb[@"green"] floatValue]; float b = [rgb[@"blue"] floatValue]; return [NSColor colorWithRed:(r / 255.f) green:(g / 255.f) blue:(b / 255.f) alpha:1.0]; }; JSValue *value = [context evaluateScript:@"makeNSColor({red:12, green:23, blue:67})"];
一個JSValue實例就是一個JavaScript值的引用。使用JSValue類在JavaScript和native代碼之間轉換一些基本類型的數據(好比數值和字符串)。你也可使用這個類去建立包裝了自定義類的native對象的JavaScript對象,或者建立由native方法或者block實現的JavaScript函數。
每一個JSValue實例都來源於一個表明JavaScript執行環境的JSContext對象,這個執行環境就包含了這個JSValue對應的值。每一個JSValue對象都持有其JSContext對象的強引用,只要有任何一個與特定JSContext關聯的JSValue被持有(retain),這個JSContext就會一直存活。經過調用JSValue的實例方法返回的其餘的JSValue對象都屬於與最始的JSValue相同的JSContext。
每一個JSValue都經過其JSContext間接關聯了一個特定的表明執行資源基礎的JSVirtualMachine對象。你只能將一個JSValue對象傳給由相同虛擬機管理(host)的JSValue或者JSContext的實例方法。若是嘗試把一個虛擬機的JSValue傳給另外一個虛擬機,將會觸發一個Objective-C異常。
JSValue提供了一系列的方法將native與JavaScript的數據類型進行相互轉換:
NSDictionary對象以及其包含的keys與JavaScript中的對應名稱的屬性相互轉換。key所對應的值也會遞歸地進行拷貝和轉換。
[context evaluateScript:@"var color = {red:230, green:90, blue:100}"]; //js->native 給你看個人顏色 JSValue *colorValue = context[@"color"]; NSLog(@"r=%@, g=%@, b=%@", colorValue[@"red"], colorValue[@"green"], colorValue[@"blue"]); NSDictionary *colorDic = [colorValue toDictionary]; NSLog(@"r=%@, g=%@, b=%@", colorDic[@"red"], colorDic[@"green"], colorDic[@"blue"]); //native->js 給你點顏色看看 context[@"color"] = @{@"red":@(0), @"green":@(0), @"blue":@(0)}; [context evaluateScript:@"log('r:'+color.red+'g:'+color.green+' b:'+color.blue)"]; Output: r=230, g=90, b=100 r=230, g=90, b=100 r:0 g:0 b:0
可見,JS中的對象能夠直接轉換成Objective-C中的NSDictionary,NSDictionary傳入JavaScript也能夠直接看成對象被使用。
NSArray對象與JavaScript中的array相互轉轉。其子元素也會遞歸地進行拷貝和轉換。
[context evaluateScript:@「var friends = ['Alice','Jenny','XiaoMing']"]; //js->native 你說哪一個是真愛? JSValue *friendsValue = context[@"friends"]; NSLog(@"%@, %@, %@", friendsValue[0], friendsValue[1], friendsValue[2]); NSArray *friendsArray = [friendsValue toArray]; NSLog(@"%@, %@, %@", friendsArray[0], friendsArray[1], friendsArray[2]); //native->js 我覺XiaoMing和不不錯,給你再推薦個Jimmy context[@"girlFriends"] = @[friendsArray[2], @"Jimmy"]; [context evaluateScript:@"log('girlFriends :'+girlFriends[0]+' '+girlFriends[1])"];
Output:
Alice, Jenny, XiaoMing Alice, Jenny, XiaoMing girlFriends : XiaoMing Jimmy
Objective-C中的block轉換成JavaScript中的function對象。參數以及返回類型使用相同的規則轉換。
將一個表明native的block或者方法的JavaScript function進行轉換將會獲得那個block或方法。
其餘的JavaScript函數將會被轉換爲一個空的dictionary。由於JavaScript函數也是一個對象。
對於全部其餘native的對象類型,JavaScriptCore都會建立一個擁有constructor原型鏈的wrapper對象,用來反映native類型的繼承關係。默認狀況下,native對象的屬性和方法並不會導出給其對應的JavaScript wrapper對象。經過JSExport協議可選擇性地導出屬性和方法。
後面會詳細講解對象類型的轉換。
此文已由騰訊雲+社區在各渠道發佈
獲取更多新鮮技術乾貨,能夠關注咱們騰訊雲技術社區-雲加社區官方號及知乎機構號