寫在前面
本篇文章是對我一次組內(nèi)分享的整理重归,大部分圖片都是直接從keynote上截圖下來的桅狠,本來有很多炫酷動效的奸笤,看博客的話就全靠腦補了痕檬,多圖預(yù)警 :)
概覽
- JavaScriptCore 簡介
- Objective-C 與 JavaScript 交互
- JavaScript 與 Objective-C 交互
- 內(nèi)存管理
- 多線程
一. JavaScriptCore 簡介
1.1 JavaScriptCore 和 JavaScriptCore 框架
首先要區(qū)分JavaScriptCore 和 JavaScriptCore 框架(同后文中的JSCore)
JavaScriptCore框架 是一個蘋果在iOS7引入的框架,該框架讓 Objective-C 和 JavaScript 代碼直接的交互變得更加的簡單方便。
而JavaScriptCore是蘋果Safari瀏覽器的JavaScript引擎青伤,或許你聽過Google的V8引擎督怜,在WWDC上蘋果演示了最新的Safari,據(jù)說JavaScript處理速度已經(jīng)大大超越了Google的Chrome潮模,這就意味著JavaScriptCore在性能上也不輸V8了亮蛔。
JavaScriptCore框架其實就是基于webkit中以C/C++實現(xiàn)的JavaScriptCore的一個包裝,在舊版本iOS開發(fā)中擎厢,很多開發(fā)者也會自行將webkit的庫引入項目編譯使用【苛鳎現(xiàn)在iOS7把它當(dāng)成了標(biāo)準(zhǔn)庫。
JavaScriptCore框架在OS X平臺上很早就存在的动遭,不過接口都是純C語言的芬探,而在之前的iOS平臺(iOS7之前),蘋果沒有開放該框架厘惦,所以不少需要在iOS app中處理JavaScript的都得自己從開源的WebKit中編譯出JavaScriptCore.a偷仿,接口也是純C語言的∠叮可能是蘋果發(fā)現(xiàn)越來越多的程序使用了自編譯的JavaScriptCore酝静,干脆做個順?biāo)饲閷avaScriptCore框架開放了,同時還提供了Objective-C的封裝接口羡玛。
本篇文章將要討論的就是基于Objective-C封裝的JavaScriptCore框架别智,也就是我們開發(fā)iOS app時使用的JavaScriptCore框架。
1.2 JavaScriptCore API Goals
蘋果基于 Objective-C 封裝的 JavaScriptCore 接口有3個目標(biāo):
自動化的:使用這些API的時候稼稿,很多事都是蘋果幫我們做了的薄榛,比如在OC和JS之間交互的時候,很多時候會自動幫我們轉(zhuǎn)換類型让歼。
安全的:我們都知道JS是一門動態(tài)類型的語言敞恋,也就是說那你從JS傳遞到OC中的值可能是任何值,而OC是靜態(tài)類型的語言谋右,它可不能動態(tài)的接收各種類型的值硬猫,但是你可以隨便傳,程序并不會奔潰改执,蘋果希望這些API是不容易出錯的浦徊,就算出錯了,也是不會導(dǎo)致程序奔潰的天梧,事實上也是如此。還有一點就是這些API本身是線程安全的霞丧,我們后面會說到呢岗。
高保真的:前面兩點比較好理解,但是這個高保真是作何解釋呢,很簡單后豫,就是蘋果希望我們在使用這些API與JS交互的時候悉尾,寫OC的時候就像在寫OC,寫JS的時候就像在寫JS挫酿,不需要一些奇怪的語法构眯,這點我們后面會用實例說明。
二. Objective-C 與 JavaScript 交互
先看個小demo早龟,很簡單的幾行代碼惫霸,首先,我們引入了JavaScriptCore框架葱弟,然后創(chuàng)建了一個叫JSContext的類的對象壹店,再然后用這個JSContext執(zhí)行了一個段JS代碼2 + 2
,這里的JS代碼是以字符串的形式傳入的芝加,執(zhí)行后得到一個JSValue類型的值硅卢,最后,將這個JSVlaue類型的值轉(zhuǎn)換成整型并輸出藏杖。
輸出結(jié)果如下将塑,這樣我們就用OC調(diào)用了一段JS代碼,很簡單對吧:)
這個 demo 里面出現(xiàn)了2個之前沒見過的類蝌麸,一個叫JSContext点寥,一個叫JSValue,下面我們一個一個說下祥楣。
2.1 JSContext
JSContext 是JS代碼的執(zhí)行環(huán)境
JSContext 為JS代碼的執(zhí)行提供了上下文環(huán)境开财,通過jSCore執(zhí)行的JS代碼都得通過JSContext來執(zhí)行。JSContext對應(yīng)于一個 JS 中的全局對象
JSContext對應(yīng)著一個全局對象误褪,相當(dāng)于瀏覽器中的window對象责鳍,JSContext中有一個GlobalObject屬性,實際上JS代碼都是在這個GlobalObject上執(zhí)行的兽间,但是為了容易理解历葛,可以把JSContext等價于全局對象。
你可以把他想象成這樣:
2.2 JSValue
-
JSValue 是對 JS 值的包裝
JSValue 顧名思義嘀略,就是JS值嘛恤溶,但是JS中的值拿到OC中是不能直接用的,需要包裝一下帜羊,這個JSValue就是對JS值的包裝咒程,一個JSValue對應(yīng)著一個JS值,這個JS值可能是JS中的number讼育,boolean等基本類型帐姻,也可能是對象稠集,函數(shù),甚至可以是undefined饥瓷,或者null剥纷。如下圖:
其實,就相當(dāng)于JS 中的 var呢铆。
-
JSValue存在于JSContext中
JSValue是不能獨立存在的晦鞋,它必須存在于某一個JSContext中,就像瀏覽器中所有的元素都包含于Window對象中一樣棺克,一個JSContext中可以包含多個JSValue悠垛。就像這樣:
Tips: 圖中的 λ (lambda) 符號表示匿名函數(shù),閉包的意思逆航,它的大寫形式為 ^ 鼎文,這就是為什么 OC 中 Block 定義都有一個 ^ 符號。
-
都是強引用
這點很關(guān)鍵因俐,JSValue對其對應(yīng)的JS值和其所屬的JSContext對象都是強引用的關(guān)系拇惋。因為jSValue需要這兩個東西來執(zhí)行JS代碼,所以JSValue會一直持有著它們抹剩。
下面這張圖可以更直觀的描述出它們之間的關(guān)系:
通過下面這些方法來創(chuàng)建一個JSValue對象:
你可以將OC中的類型撑帖,轉(zhuǎn)換成JS中的對應(yīng)的類型(參見前面那個類型對照表),并包裝在JSValue中澳眷,包括基本類型胡嘿,Null和undfined。
或者你也可以創(chuàng)建一個新的對象钳踊,數(shù)組衷敌,正則表達式,錯誤拓瞪,這幾個方法達到的效果就相當(dāng)于在JS中寫 var a = new Array();
也可以將一個OC對象缴罗,轉(zhuǎn)成JS中的對象,但是這樣轉(zhuǎn)換后的對象中的屬性和方法祭埂,在JS中是獲取不到的面氓,怎樣才能讓JS中獲取的OC對象中的屬性和方法,我們后面再說蛆橡。
2.3 實際使用
再看一個Demo:
首先是一段JS代碼舌界,一個簡單的遞歸函數(shù),計算階乘的:
然后泰演,如果我們想在OC中調(diào)用這個JS中的函數(shù)該如何做呢呻拌?如下:
首先,從bundle中加載這段JS代碼睦焕。
然后柏锄,創(chuàng)建一個JSContext酿箭,并用他來執(zhí)行這段JS代碼,這句的效果就相當(dāng)于在一個全局對象中聲明了一個叫fatorial
的函數(shù)趾娃,但是沒有調(diào)用它,只是聲明缔御,所以執(zhí)行完這段JS代碼后沒有返回值抬闷。
再從這個全局對象中獲取這個函數(shù),這里我們用到了一種類似字典的下標(biāo)寫法來獲取對應(yīng)的JS函數(shù)耕突,就像在一個字典中取這個key對應(yīng)的value一樣簡單笤成,實際上,JS中的對象就是以 key : Value
的形式存儲屬性的眷茁,且JS中的object對象類型炕泳,對應(yīng)到OC中就是字典類型,所以這種寫法自然且合理上祈。
這種類似字典的下標(biāo)方式不僅可以取值培遵,也可以存值。不僅可以作用于Context登刺,也可以作用與JSValue籽腕,他會用中括號中填的key值去匹配JSValue包含的JS值中有沒有對應(yīng)的屬性字段,找到了就返回纸俭,沒找到就返回undefined皇耗。
然后,我們拿到了包裝這個階乘函數(shù)的的JSValue對象揍很,在其上調(diào)用callWithArguments方法郎楼,即可調(diào)用該函數(shù),這個方法接收一個數(shù)組為參數(shù)窒悔,這是因為JS中的函數(shù)的參數(shù)都不是固定的呜袁,我們構(gòu)建了一個數(shù)組,并把NSNumber類型的5傳了過去蛉迹,然而JS肯定是不知道什么是NSNumber的傅寡,但是別擔(dān)心,JSCore會幫我們自動轉(zhuǎn)換JS中對應(yīng)的類型北救, 這里會把NSNumber類型的5轉(zhuǎn)成JS中number類型的5荐操,然后再去調(diào)用這個函數(shù)(這就是前面說的API目標(biāo)中自動化的體現(xiàn))。
最后珍策,如果函數(shù)有返回值托启,就會將函數(shù)返回值返回,如果沒有返回值則返回undefined攘宙,當(dāng)然在經(jīng)過JSCore之后屯耸,這些JS中的類型都被包裝成了JSValue拐迁,最后我們拿到返回的JSValue對象,轉(zhuǎn)成對應(yīng)的類型并輸出疗绣。這里結(jié)果是120线召,我就不貼出來了。
三. JavaScript 與 Objective-C 交互
JavaScript 與 Objective-C 交互主要通過2種方式:
- Block : 第一種方式是使用block多矮,block也可以稱作閉包和匿名函數(shù)缓淹,使用block可以很方便的將OC中的單個方法暴露給JS調(diào)用,具體實現(xiàn)我們稍后再說塔逃。
-
JSExport 協(xié)議 : 第二種方式讯壶,是使用
JSExport
協(xié)議,可以將OC的中某個對象直接暴露給JS使用湾盗,而且在JS中使用就像調(diào)用JS的對象一樣自然伏蚊。
簡而言之,Block是用來暴露單個方法的格粪,而JSExport 協(xié)議可以暴露一個OC對象躏吊,下面我們詳細說一下這兩種方式。
3.1 Block
上面說過匀借,使用Block可以很方便的將OC中的單個方法(即Block)暴露給JS調(diào)用颜阐,JSCore會自動將這個Block包裝成一個JS方法,具體怎么個包裝法呢吓肋?上Demo:這就是一段將OC Block暴露給JS的代碼凳怨,很簡單是不是,就像這樣是鬼,我們用前面提過的這種類似字典的寫法把一個OC Bock注入了context中肤舞,這個block接收一個NSDictionary類型的參數(shù),并返回了一個NSColor類型的對象(NSColor是APPkit中的類均蜜,是在Mac 開發(fā)中用的李剖,相當(dāng)于UIkit中的NSColor)。
這樣寫的話囤耳,會發(fā)生什么呢篙顺?請看下圖
我們有一個JSContext,然后將一個OCBlock注入進去充择,JSCore會自動在全局對象中(因為是直接在Context上賦值的德玫,context對應(yīng)于全局對象)創(chuàng)建一個叫makeNSColor的函數(shù),將這個Block包裝起來椎麦。
然后宰僧,在JS中,我們來調(diào)用這個暴露過來的block观挎,其實直接調(diào)用的是那個封裝著Block的MakeNSColor方法琴儿。
這里有一個叫colorForWord的JS方法段化,它接收一個word參數(shù),這個colorMap是一個JS對象造成,里面按顏色名字保存著一些色值信息显熏,這些色值信息也是一個個的JS對象,這個ColorForWord函數(shù)就是通過顏色名字來取得對應(yīng)的顏色對象谜疤。然后這函數(shù)里面調(diào)用了MakeNSColor方法佃延,并傳入從colorMap中根據(jù)word字段取出來的顏色對象,注意這個顏色對象是一個JS對象夷磕,是一個object類型,但是我們傳進來的Block接收的是一個NSDIctionary類型的參數(shù)啊仔沿,不用擔(dān)心坐桩,這時JSCore會自動幫我們把JS對象類型轉(zhuǎn)成NSDictionary類型,就像前面那個表里寫的一樣封锉,NSDictionary對應(yīng)著JS中的Object類型绵跷。
現(xiàn)在,我們有一個包裝著Block的JS函數(shù)
makeNSColor
成福,然后又有一個colorForWrod
函數(shù)來調(diào)用它碾局,具體過程就像這樣:圖從左邊看起,
colorForWrod
調(diào)用makeNSColor
奴艾,傳過去的參數(shù)是JS Object類型(從colorMap中取出的顏色對象)净当,JSCore會將這個傳過來的Object參數(shù)轉(zhuǎn)換成NSDictionary類型,然后makeNSColor
用其去調(diào)用內(nèi)部包裝的Block
蕴潦,Block
返回一個NSColor(NSObject)類型的返回值像啼,JScore會將其轉(zhuǎn)換成一個wrapper Object
(其實也是JS Object類型),返回給colorForWrod
潭苞。
如果我們在OC中調(diào)用這個colorForWrod
函數(shù)忽冻,會是什么樣子呢?如下圖:
OC Caller
去調(diào)用這個colorForWrod
函數(shù)此疹,因為colorForWrod
函數(shù)接收的是一個String
類型那個參數(shù)word僧诚,OC Caller
傳過去的是一個NSString
類型的參數(shù),JSCore轉(zhuǎn)換成對應(yīng)的String
類型蝗碎。然后colorForWrod
函數(shù)繼續(xù)向下調(diào)用湖笨,就像上面說的,知道其拿到返回的wrapper Object
衍菱,它將wrapper Object
返回給調(diào)用它的OC Caller
赶么,JSCore又會在這時候把wrapper Object
轉(zhuǎn)成JSValue類型,最后再OC中通過對JSValue調(diào)用對應(yīng)的轉(zhuǎn)換方法脊串,即可拿到里面包裝的值辫呻,這里我們調(diào)用- toObject
方法清钥,最后會得到一個NSColor
對象,即從最開始那個暴露給JS的Block中返回的對象放闺。
通過一步一步的分析祟昭,我們發(fā)現(xiàn),JavaScriptCore會在JS與OC交界處傳遞數(shù)據(jù)時做相應(yīng)的類型轉(zhuǎn)換怖侦,轉(zhuǎn)換規(guī)則如前面的OC-JS類型對照表篡悟。
3.1.1 使用 Block 的坑
使用Block暴露方法很方便,但是有2個坑需要注意一下:
- 不要在Block中直接使用JSValue
- 不要在Block中直接使用JSContext
因為Block會強引用它里面用到的外部變量匾寝,如果直接在Block中使用JSValue的話搬葬,那么這個JSvalue就會被這個Block強引用,而每個JSValue都是強引用著它所屬的那個JSContext的,這是前面說過的,而這個Block又是注入到這個Context中执解,所以這個Block會被context強引用退盯,這樣會造成循環(huán)引用,導(dǎo)致內(nèi)存泄露。不能直接使用JSContext的原因同理。
那怎么辦呢,針對第一點床三,建議把JSValue當(dāng)做參數(shù)傳到Block中,而不是直接在Block內(nèi)部使用杨幼,這樣Block就不會強引用JSValue了撇簿。
針對第二點,可以使用[JSContext currentContext] 方法來獲取當(dāng)前的Context推汽。
3.2 JSExport 協(xié)議
3.2.1 介紹
然后是JS和OC交互的第二種方式:JSExport 協(xié)議
补疑,通過JSExport 協(xié)議可以很方便的將OC中的對象暴露給JS使用,且在JS中用起來就和JS對象一樣歹撒。
3.2.2 使用
舉個栗子莲组,我們在Objective-C中有一個MyPoint類,它有兩個double類型的屬性暖夭,x,y锹杈,一個實例方法description 和一個類方法 makePointWithX: Y:
如果我們使用JSExport協(xié)議把這個類的對象暴露給JS,那么在JS中迈着,我們怎么使用這個暴露過來的JS對象呢竭望?他的屬性可以直接調(diào)用,就像調(diào)用JS對象的屬性一樣裕菠,他的實例方法也可以直接調(diào)用咬清,就像調(diào)用JS對象中的方法一樣,然后他的類方法,也可以直接用某個全局對象直接調(diào)用旧烧。就像普通的JS一樣影钉,但是操作的卻是一個OC對象。
實現(xiàn)這些只需要寫這樣一句話掘剪。
@protocol MyPointExports <JSExport>
聲明一個自定義的協(xié)議并繼承自JSExport協(xié)議平委。然后當(dāng)你把實現(xiàn)這個自定義協(xié)議的對象暴露給JS時,JS就能像使用原生對象一樣使用OC對象了夺谁,也就是前面說的API目標(biāo)之高保真廉赔。
需要注意的是,OC中的函數(shù)聲明格式與JS中的不太一樣(應(yīng)該說和大部分語言都不一樣匾鸥。蜡塌。),OC函數(shù)中多個參數(shù)是用冒號:
聲明的勿负,這顯然不能直接暴露給JS調(diào)用岗照,這不高保真。笆环。
所以需要對帶參數(shù)的方法名做一些調(diào)整,當(dāng)我們暴露一個帶參數(shù)的OC方法給JS時厚者,JSCore會用以下兩個規(guī)則生成一個對應(yīng)的JS函數(shù):
- 移除所有的冒號
- 將跟在冒號后面的第一個小寫字母大寫
比如上面的那個類方法躁劣,轉(zhuǎn)換之前方法名應(yīng)該是 makePointWithX:y:
,在JS中生成的對應(yīng)的方法名就會變成 makePointWithXY
库菲。
蘋果知道這種不一致可能會逼死某些強迫癥账忘。。所以加了一個宏JSExportAs
來處理這種情況熙宇,它的作用是:給JSCore在JS中為OC方法生成的對應(yīng)方法指定名字鳖擒。
比如,還是上面這個方法makePointWithX:y:
烫止,可以這樣寫:
這個
makePoint
就是給JS中方法指定的名字蒋荚,這樣,在JS中就能直接調(diào)用makePoint
來調(diào)用這個OC方法makePointWithX:y:
了馆蠕。
注意:這個宏只對帶參數(shù)的OC方法有效期升。
然后,這里有一個JSExport協(xié)議使用的 小Demo 有興趣的可以看看互躬,用起來其實挺簡單的播赁。
3.2.3 探究
但是,光會用可不行吼渡,這個JSExoprt協(xié)議到底做了什么呢容为?
當(dāng)你聲明一個繼承自JSExport的自定義協(xié)議時,就是在告訴JSCore,這個自定義協(xié)議中聲明的屬性坎背,實例方法和類方法需要被暴露給JS使用替劈。(不在這個協(xié)議中的方法不會被暴露出去。)
當(dāng)你把實現(xiàn)這個協(xié)議的類的對象暴露給JS時沼瘫,JS中會生成一個對應(yīng)的JS對象抬纸,然后,JSCore會按照這個協(xié)議中聲明的內(nèi)容耿戚,去遍歷實現(xiàn)這個協(xié)議的類湿故,把協(xié)議中聲明的屬性,轉(zhuǎn)換成JS 對象中的屬性膜蛔,實質(zhì)上是轉(zhuǎn)換成getter 和 setter 方法坛猪,轉(zhuǎn)換方法和之前說的block類似,創(chuàng)建一個JS方法包裝著OC中的方法皂股,然后協(xié)議中聲明的實例方法墅茉,轉(zhuǎn)換成JS對象上的實例方法,類方法轉(zhuǎn)換成JS中某個全局對象上的方法呜呐。
那這里說的某個全局對象到底是什么呢就斤?這涉及到JS中的知識:
- Prototype & Constructor
在傳統(tǒng)的基于Class的語言如Java、C++中蘑辑,繼承的本質(zhì)是擴展一個已有的Class洋机,并生成新的Subclass。但是洋魂,JS中是沒有class類型的绷旗,那JS種怎么實現(xiàn)繼承呢,答案就是通過原型對象(Prototype)副砍。
JavaScript對每個創(chuàng)建的對象都會設(shè)置一個原型衔肢,指向它的原型對象。對象會從其原型對象上繼承屬性和方法豁翎。
當(dāng)我們用obj.xxx訪問一個對象的屬性時角骤,JavaScript引擎先在當(dāng)前對象上查找該屬性,如果沒有找到谨垃,就到其原型對象上找启搂,如果還沒有找到,就一直上溯到object.prototype對象刘陶,最后胳赌,如果還沒有找到,就只能返回undefined匙隔。
原型對象也是一個對象疑苫,他有一個構(gòu)造函數(shù)Constructor,就是用來創(chuàng)建對象的。
假如我們有一個Student構(gòu)造函數(shù)捍掺,然后用它創(chuàng)建了一個對象 xiaoming撼短,那么小明這個對象的原型鏈就是這樣的。
function Student(name) {
this.name = name;
this.hello = function () {
alert('Hello, ' + this.name + '!');
}
}
var xiaoming = new Student('小明');
xiaoming ----> Student.prototype ----> Object.prototype ----> null
再詳細一點如下圖挺勿,xiaohong是Student函數(shù)構(gòu)造的另一個對象曲横,紅色箭頭是原型鏈。注意不瓶,Student.prototype指向的對象就是xiaoming禾嫉、xiaohong的原型對象,這個原型對象自己還有個屬性constructor蚊丐,指向Student函數(shù)本身熙参。
另外,函數(shù)Student恰好有個屬性prototype指向xiaoming麦备、xiaohong的原型對象孽椰,但是xiaoming、xiaohong這些對象可沒有prototype這個屬性凛篙,不過可以用proto這個非標(biāo)準(zhǔn)用法來查看黍匾。
這樣我們就認(rèn)為xiaoming、xiaohong這些對象“繼承”自Student呛梆。
xiaoming的原型對象上又有一根紅色箭頭指向Object.prototype膀捷,這樣我們就說Student“繼承”自O(shè)bject。Object.prototype的原型對象又指向null削彬。
更多關(guān)于Prototype和Constructor的知識可以看這里。
不知道有沒有覺得秀仲,這里的原型對象融痛,有點像OC中的類,而構(gòu)造函數(shù)神僵,則有點像OC中的元類雁刷,OC中類方法都是放在元類當(dāng)中的,所以前面說的某個全局對象就是JS中的構(gòu)造函數(shù)保礼。
這里我畫了一張圖沛励,用來描述使用JSExport協(xié)議暴露對象時,OC和JS中的對應(yīng)關(guān)系:
我們有一個MyPoint類的對象point炮障,當(dāng)我們用JSExport協(xié)議將這個OC對象暴露給JS時目派,JSCore首先會在JS上下文環(huán)境中為該類生成一個對應(yīng)的原型對象和構(gòu)造函數(shù),然后JSCore會掃描這個類胁赢,把其中在JSExport協(xié)議中聲明的內(nèi)容暴露給JS企蹭,屬性(即getter和setter方法)會被添加到原型對象上,而類方法會被添加到到這個構(gòu)造函數(shù)上,這個放的位置谅摄,就正好對應(yīng)了OC中的類和元類徒河。
然后就像前面那張圖一樣,原型對象中有一個constructor屬性送漠,指向構(gòu)造函數(shù)顽照,構(gòu)造函數(shù)中有一個prototype屬性指向原型對象。我們又知道闽寡,MyPoint類是繼承與NSObject類的代兵,JSCore也會為暴露的類的父類創(chuàng)建原型對象和構(gòu)造函數(shù),NSObject類的原型對象就是JS中Object類的原型對象下隧。
每一個原型對象都有一個屬性叫Prototype奢人,大寫的P,他是一個指針淆院,用來表示JS中的繼承關(guān)系(即原型鏈)何乎,MyPoint類的原型對象會指向NSObject類的原型對象。而NSObject的原型對象土辩,及Object的原型對象會指向null支救。最后再用Mypoint類的構(gòu)造函數(shù)和原型對象在JS中去生成一個與OC中point對象對應(yīng)的JS對象。
這樣就在JS中用JS的體系結(jié)構(gòu)構(gòu)造了與OC中一樣的類繼承關(guān)系拷淘。
這就是使用JSExport 協(xié)議暴露的OC對象在JS中可以像調(diào)用JS對象一樣的關(guān)鍵所在各墨。
四. 內(nèi)存管理
我們都知道,Objective-C 用的是ARC (Automatic Reference Counting)启涯,不能自動解決循環(huán)引用問題(retain cycle)贬堵,需要程序員手動處理,而JavaScript 用的是GC (準(zhǔn)確的說是 Tracing Garbage Collection)结洼,所有的引用都是強引用黎做,但是垃圾回收器會幫我解決循環(huán)引用問題,JavaScriptCore 也一樣松忍,一般來說蒸殿,大多數(shù)時候不需要我們?nèi)ナ謩庸芾韮?nèi)存。
但是下面2種情況需要注意一下:
不要在JS中給OC對象增加成員變量鸣峭,這句話的意思就是說宏所,當(dāng)我們將一個OC對象暴露給JS后,就像前面說的使用JSExport協(xié)議摊溶,我們能想操縱JS對象一樣操縱OC對象爬骤,但是這時候,不要在JS中給這個OC對象添加成員變量莫换,因為這個動作產(chǎn)生的后果就是盖腕,只會在JS中為這個OC對象增加一個額外的成員變量赫冬,但是OC中并不會同步增加。所以說這樣做并沒有什么意義溃列,還有可能造成一些奇怪的內(nèi)存管理問題劲厌。
-
OC對象不要直接強引用JSValue對象,這句話再說直白點听隐,就是不要直接將一個JSValue類型的對象當(dāng)成屬性或者成員變量保存在一個OC對象中补鼻,尤其是這個OC對象還暴露給JS的時候。這樣會造成循環(huán)引用雅任。如下圖:
如何解決這個問題呢风范?你可能會想,不能強引用沪么, 那就弱引用唄硼婿,就像圖上這樣,但是這樣做也是不行的禽车,因為JSValue沒用對象引用他寇漫,他就會被釋放了。
那怎么辦殉摔?分析一下州胳,在這里,我們需要一種弱的引用關(guān)系逸月,因為強引用會造成循環(huán)引用栓撞,但是又不能讓這個JSValue因無人引用它而被釋放。簡而言之就是碗硬,弱引用但能保持JSValue不被釋放瓤湘。
于是,蘋果退出了一種新的引用關(guān)系恩尾,叫conditional retain
岭粤,有條件的強引用,通過這種引用就能實現(xiàn)我們前面分析所需要的效果特笋,而JSManagedValue
就是蘋果用來實現(xiàn)conditional retain的類。
4.1 JSManagedValue
這是JSManagedValue的一般使用步驟:
首先巾兆,用JSValue創(chuàng)建一個JSManagedValue對象猎物,JSManagedValue里面其實就是包著一個JSValue對象,可以通過它里面一個只讀的value屬性取到角塑,這一步其實是添加一個對JSValue的弱引用蔫磨。
-
如果只有第一步,這個JSValue會在其對應(yīng)的JS值被垃圾回收器回收之后被釋放圃伶,這樣效果就和弱引用一樣堤如,所以還需要加一步蒲列,在虛擬機上為這個JSManagedValue對象添加Owner(這個虛擬機就是給JS執(zhí)行提供資源的,待會再講)搀罢,這樣做之后蝗岖,就給JSValue增加一個強關(guān)系,只要以下兩點有一點成立榔至,這個JSManagedValue里面包含的JSValue就不會被釋放:
- JSValue對應(yīng)的JS值沒有被垃圾回收器回收
- Owner對象沒有被釋放
這樣做抵赢,就即避免了循環(huán)引用,又保證了JSValue不會因為弱引用而被立刻釋放唧取。
五. 多線程
說多線程之前得先說下另一個類 JSVirtualMachine
, 它為JavaScript的運行提供了底層資源铅鲤,有自己獨立的堆棧以及垃圾回收機制。
JSVirtualMachine
還是JSContext的容器枫弟,可以包含若干個JSContext邢享,在一個進程中,你可以有多個JSVirtualMachine淡诗,里面包含著若干個JSContext骇塘,而JSContext中又有若干個JSValue,他們的包含關(guān)系如下圖:
需要注意的是袜漩,你可以在同一個JSVirtualMachine的不同JSContext中绪爸,互相傳遞JSValue,但是不能再不同的JSVirtualMachine中的JSContext之間傳遞JSValue宙攻。
這是因為奠货,每個JSVirtualMachine都有自己獨立的堆棧和垃圾回收器,一個JSVirtualMachine的垃圾回收器不知道怎么處理從另一個堆棧傳過來的值座掘。
說回多線程递惋,JavaScriptCore提供的API本身就是線程安全的。
你可以在不同的線程中溢陪,創(chuàng)建JSValue萍虽,用JSContext執(zhí)行JS語句,但是當(dāng)一個線程正在執(zhí)行JS語句時形真,其他線程想要使用這個正在執(zhí)行JS語句的JSContext所屬的JSVirtualMachine就必須得等待杉编,等待前前一個線程執(zhí)行完,才能使用這個JSVirtualMachine咆霜。
當(dāng)然邓馒,這個強制串行的粒度是JSVirtualMachine,如果你想要在不用線程中并發(fā)執(zhí)行JS代碼蛾坯,可以為不同的線程創(chuàng)建不同JSVirtualMachine光酣。
最后再補充一點,就是關(guān)于如何獲取 UIWebView 中的 JSContext脉课,由于篇幅問題這里就不贅述了救军,推薦一篇文章财异。
參考
Integrating JavaScript into Native Apps
Java?Script?Core API Reference
廖雪峰的JavaScript教程