概覽
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):
JavaScriptCore API Goals
自動化的:使用這些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等價于全局對象。
你可以把他想象成這樣:
JSContext
2.2 JSValue
JSValue 是對 JS 值的包裝
JSValue 顧名思義甩恼,就是JS值嘛蟀瞧,但是JS中的值拿到OC中是不能直接用的,需要包裝一下条摸,這個JSValue就是對JS值的包裝悦污,一個JSValue對應(yīng)著一個JS值,這個JS值可能是JS中的number钉蒲,boolean等基本類型切端,也可能是對象,函數(shù)子巾,甚至可以是undefined帆赢,或者null。如下圖:
OC-JS類型對照表
其實线梗,就相當(dāng)于JS 中的 var椰于。
JSValue存在于JSContext中
JSValue是不能獨立存在的,它必須存在于某一個JSContext中仪搔,就像瀏覽器中所有的元素都包含于Window對象中一樣瘾婿,一個JSContext中可以包含多個JSValue。就像這樣:
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ù),計算階乘的:
Demo_JS
然后秸妥,如果我們想在OC中調(diào)用這個JS中的函數(shù)該如何做呢滚停?如下:
Demo_OC
首先,從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
聲明一個自定義的協(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候衍,那么小明這個對象的原型鏈就是這樣的笼蛛。
functionStudent(name){this.name=name;this.hello=function(){alert('Hello, '+this.name+'!');}}varxiaoming=newStudent('小明');
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)用法來查看散罕。
這樣我們就認為xiaoming分歇、xiaohong這些對象“繼承”自Student。
xiaoming的原型對象上又有一根紅色箭頭指向Object.prototype欧漱,這樣我們就說Student“繼承”自O(shè)bject职抡。Object.prototype的原型對象又指向null。
JS原型鏈
更多關(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珠月。
作者:_James_
鏈接:http://www.reibang.com/p/3f5dc8042dfc