本文由我們團(tuán)隊(duì)的 糾結(jié)倫 童鞋撰寫。
寫在前面
本篇文章是對我一次組內(nèi)分享的整理,大部分圖片都是直接從keynote上截圖下來的,本來有很多炫酷動(dòng)效的,看博客的話就全靠腦補(bǔ)了,多圖預(yù)警 :)
概覽
- JavaScriptCore 簡介
- Objective-C 與 JavaScript 交互
- JavaScript 與 Objective-C 交互
- 內(nèi)存管理
- 多線程
一. JavaScriptCore 簡介
1.1 JavaScriptCore 和 JavaScriptCore 框架
首先要區(qū)分JavaScriptCore 和 JavaScriptCore 框架(同后文中的JSCore)
JavaScriptCore框架 是一個(gè)蘋果在iOS7引入的框架,該框架讓 Objective-C 和 JavaScript 代碼直接的交互變得更加的簡單方便。
而JavaScriptCore是蘋果Safari瀏覽器的JavaScript引擎,或許你聽過Google的V8引擎,在WWDC上蘋果演示了最新的Safari,據(jù)說JavaScript處理速度已經(jīng)大大超越了Google的Chrome,這就意味著JavaScriptCore在性能上也不輸V8了。
JavaScriptCore框架其實(shí)就是基于webkit中以C/C++實(shí)現(xiàn)的JavaScriptCore的一個(gè)包裝,在舊版本iOS開發(fā)中,很多開發(fā)者也會(huì)自行將webkit的庫引入項(xiàng)目編譯使用。現(xiàn)在iOS7把它當(dāng)成了標(biāo)準(zhǔn)庫。
JavaScriptCore框架在OS X平臺(tái)上很早就存在的,不過接口都是純C語言的,而在之前的iOS平臺(tái)(iOS7之前),蘋果沒有開放該框架,所以不少需要在iOS app中處理JavaScript的都得自己從開源的WebKit中編譯出JavaScriptCore.a,接口也是純C語言的。可能是蘋果發(fā)現(xiàn)越來越多的程序使用了自編譯的JavaScriptCore,干脆做個(gè)順?biāo)饲閷avaScriptCore框架開放了,同時(shí)還提供了Objective-C的封裝接口。
本篇文章將要討論的就是基于Objective-C封裝的JavaScriptCore框架,也就是我們開發(fā)iOS app時(shí)使用的JavaScriptCore框架。
1.2 JavaScriptCore API Goals
蘋果基于 Objective-C 封裝的 JavaScriptCore 接口有3個(gè)目標(biāo):
自動(dòng)化的:使用這些API的時(shí)候,很多事都是蘋果幫我們做了的,比如在OC和JS之間交互的時(shí)候,很多時(shí)候會(huì)自動(dòng)幫我們轉(zhuǎn)換類型。
安全的:我們都知道JS是一門動(dòng)態(tài)類型的語言,也就是說那你從JS傳遞到OC中的值可能是任何值,而OC是靜態(tài)類型的語言,它可不能動(dòng)態(tài)的接收各種類型的值,但是你可以隨便傳,程序并不會(huì)奔潰,蘋果希望這些API是不容易出錯(cuò)的,就算出錯(cuò)了,也是不會(huì)導(dǎo)致程序奔潰的,事實(shí)上也是如此。還有一點(diǎn)就是這些API本身是線程安全的,我們后面會(huì)說到。
高保真的:前面兩點(diǎn)比較好理解,但是這個(gè)高保真是作何解釋呢,很簡單,就是蘋果希望我們在使用這些API與JS交互的時(shí)候,寫OC的時(shí)候就像在寫OC,寫JS的時(shí)候就像在寫JS,不需要一些奇怪的語法,這點(diǎn)我們后面會(huì)用實(shí)例說明。
二. Objective-C 與 JavaScript 交互
先看個(gè)小demo,很簡單的幾行代碼,首先,我們引入了JavaScriptCore框架,然后創(chuàng)建了一個(gè)叫JSContext的類的對象,再然后用這個(gè)JSContext執(zhí)行了一個(gè)段JS代碼2 + 2
,這里的JS代碼是以字符串的形式傳入的,執(zhí)行后得到一個(gè)JSValue類型的值,最后,將這個(gè)JSVlaue類型的值轉(zhuǎn)換成整型并輸出。
輸出結(jié)果如下,這樣我們就用OC調(diào)用了一段JS代碼,很簡單對吧:)
這個(gè) demo 里面出現(xiàn)了2個(gè)之前沒見過的類,一個(gè)叫JSContext,一個(gè)叫JSValue,下面我們一個(gè)一個(gè)說下。
2.1 JSContext
JSContext 是JS代碼的執(zhí)行環(huán)境
JSContext 為JS代碼的執(zhí)行提供了上下文環(huán)境,通過jSCore執(zhí)行的JS代碼都得通過JSContext來執(zhí)行。JSContext對應(yīng)于一個(gè) JS 中的全局對象
JSContext對應(yīng)著一個(gè)全局對象,相當(dāng)于瀏覽器中的window對象,JSContext中有一個(gè)GlobalObject屬性,實(shí)際上JS代碼都是在這個(gè)GlobalObject上執(zhí)行的,但是為了容易理解,可以把JSContext等價(jià)于全局對象。
你可以把他想象成這樣:
2.2 JSValue
-
JSValue 是對 JS 值的包裝
JSValue 顧名思義,就是JS值嘛,但是JS中的值拿到OC中是不能直接用的,需要包裝一下,這個(gè)JSValue就是對JS值的包裝,一個(gè)JSValue對應(yīng)著一個(gè)JS值,這個(gè)JS值可能是JS中的number,boolean等基本類型,也可能是對象,函數(shù),甚至可以是undefined,或者null。如下圖:
OC-JS類型對照表
其實(shí),就相當(dāng)于JS 中的 var。
-
JSValue存在于JSContext中
JSValue是不能獨(dú)立存在的,它必須存在于某一個(gè)JSContext中,就像瀏覽器中所有的元素都包含于Window對象中一樣,一個(gè)JSContext中可以包含多個(gè)JSValue。就像這樣:
JSValue
Tips: 圖中的 λ (lambda) 符號(hào)表示匿名函數(shù),閉包的意思,它的大寫形式為 ^ ,這就是為什么 OC 中 Block 定義都有一個(gè) ^ 符號(hào)。
-
都是強(qiáng)引用
這點(diǎn)很關(guān)鍵,JSValue對其對應(yīng)的JS值和其所屬的JSContext對象都是強(qiáng)引用的關(guān)系。因?yàn)閖SValue需要這兩個(gè)東西來執(zhí)行JS代碼,所以JSValue會(huì)一直持有著它們。
下面這張圖可以更直觀的描述出它們之間的關(guān)系:
通過下面這些方法來創(chuàng)建一個(gè)JSValue對象:
你可以將OC中的類型,轉(zhuǎn)換成JS中的對應(yīng)的類型(參見前面那個(gè)類型對照表),并包裝在JSValue中,包括基本類型,Null和undfined。
或者你也可以創(chuàng)建一個(gè)新的對象,數(shù)組,正則表達(dá)式,錯(cuò)誤,這幾個(gè)方法達(dá)到的效果就相當(dāng)于在JS中寫 var a = new Array();
也可以將一個(gè)OC對象,轉(zhuǎn)成JS中的對象,但是這樣轉(zhuǎn)換后的對象中的屬性和方法,在JS中是獲取不到的,怎樣才能讓JS中獲取的OC對象中的屬性和方法,我們后面再說。
2.3 實(shí)際使用
再看一個(gè)Demo:
首先是一段JS代碼,一個(gè)簡單的遞歸函數(shù),計(jì)算階乘的:
然后,如果我們想在OC中調(diào)用這個(gè)JS中的函數(shù)該如何做呢?如下:
首先,從bundle中加載這段JS代碼。
然后,創(chuàng)建一個(gè)JSContext,并用他來執(zhí)行這段JS代碼,這句的效果就相當(dāng)于在一個(gè)全局對象中聲明了一個(gè)叫fatorial
的函數(shù),但是沒有調(diào)用它,只是聲明,所以執(zhí)行完這段JS代碼后沒有返回值。
再從這個(gè)全局對象中獲取這個(gè)函數(shù),這里我們用到了一種類似字典的下標(biāo)寫法來獲取對應(yīng)的JS函數(shù),就像在一個(gè)字典中取這個(gè)key對應(yīng)的value一樣簡單,實(shí)際上,JS中的對象就是以 key : Value
的形式存儲(chǔ)屬性的,且JS中的object對象類型,對應(yīng)到OC中就是字典類型,所以這種寫法自然且合理。
這種類似字典的下標(biāo)方式不僅可以取值,也可以存值。不僅可以作用于Context,也可以作用與JSValue,他會(huì)用中括號(hào)中填的key值去匹配JSValue包含的JS值中有沒有對應(yīng)的屬性字段,找到了就返回,沒找到就返回undefined。
然后,我們拿到了包裝這個(gè)階乘函數(shù)的的JSValue對象,在其上調(diào)用callWithArguments方法,即可調(diào)用該函數(shù),這個(gè)方法接收一個(gè)數(shù)組為參數(shù),這是因?yàn)镴S中的函數(shù)的參數(shù)都不是固定的,我們構(gòu)建了一個(gè)數(shù)組,并把NSNumber類型的5傳了過去,然而JS肯定是不知道什么是NSNumber的,但是別擔(dān)心,JSCore會(huì)幫我們自動(dòng)轉(zhuǎn)換JS中對應(yīng)的類型, 這里會(huì)把NSNumber類型的5轉(zhuǎn)成JS中number類型的5,然后再去調(diào)用這個(gè)函數(shù)(這就是前面說的API目標(biāo)中自動(dòng)化的體現(xiàn))。
最后,如果函數(shù)有返回值,就會(huì)將函數(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中的單個(gè)方法暴露給JS調(diào)用,具體實(shí)現(xiàn)我們稍后再說。
-
JSExport 協(xié)議 : 第二種方式,是使用
JSExport
協(xié)議,可以將OC的中某個(gè)對象直接暴露給JS使用,而且在JS中使用就像調(diào)用JS的對象一樣自然。
簡而言之,Block是用來暴露單個(gè)方法的,而JSExport 協(xié)議可以暴露一個(gè)OC對象,下面我們詳細(xì)說一下這兩種方式。
3.1 Block
上面說過,使用Block可以很方便的將OC中的單個(gè)方法(即Block)暴露給JS調(diào)用,JSCore會(huì)自動(dòng)將這個(gè)Block包裝成一個(gè)JS方法,具體怎么個(gè)包裝法呢?上Demo:
這就是一段將OC Block暴露給JS的代碼,很簡單是不是,就像這樣,我們用前面提過的這種類似字典的寫法把一個(gè)OC Bock注入了context中,這個(gè)block接收一個(gè)NSDictionary類型的參數(shù),并返回了一個(gè)NSColor類型的對象(NSColor是APPkit中的類,是在Mac 開發(fā)中用的,相當(dāng)于UIkit中的NSColor)。
這樣寫的話,會(huì)發(fā)生什么呢?請看下圖
我們有一個(gè)JSContext,然后將一個(gè)OCBlock注入進(jìn)去,JSCore會(huì)自動(dòng)在全局對象中(因?yàn)槭侵苯釉贑ontext上賦值的,context對應(yīng)于全局對象)創(chuàng)建一個(gè)叫makeNSColor的函數(shù),將這個(gè)Block包裝起來。
然后,在JS中,我們來調(diào)用這個(gè)暴露過來的block,其實(shí)直接調(diào)用的是那個(gè)封裝著Block的MakeNSColor方法。
這里有一個(gè)叫colorForWord的JS方法,它接收一個(gè)word參數(shù),這個(gè)colorMap是一個(gè)JS對象,里面按顏色名字保存著一些色值信息,這些色值信息也是一個(gè)個(gè)的JS對象,這個(gè)ColorForWord函數(shù)就是通過顏色名字來取得對應(yīng)的顏色對象。然后這函數(shù)里面調(diào)用了MakeNSColor方法,并傳入從colorMap中根據(jù)word字段取出來的顏色對象,注意這個(gè)顏色對象是一個(gè)JS對象,是一個(gè)object類型,但是我們傳進(jìn)來的Block接收的是一個(gè)NSDIctionary類型的參數(shù)啊,不用擔(dān)心,這時(shí)JSCore會(huì)自動(dòng)幫我們把JS對象類型轉(zhuǎn)成NSDictionary類型,就像前面那個(gè)表里寫的一樣,NSDictionary對應(yīng)著JS中的Object類型。
現(xiàn)在,我們有一個(gè)包裝著Block的JS函數(shù)
makeNSColor
,然后又有一個(gè)colorForWrod
函數(shù)來調(diào)用它,具體過程就像這樣:圖從左邊看起,
colorForWrod
調(diào)用makeNSColor
,傳過去的參數(shù)是JS Object類型(從colorMap中取出的顏色對象),JSCore會(huì)將這個(gè)傳過來的Object參數(shù)轉(zhuǎn)換成NSDictionary類型,然后makeNSColor
用其去調(diào)用內(nèi)部包裝的Block
,Block
返回一個(gè)NSColor(NSObject)類型的返回值,JScore會(huì)將其轉(zhuǎn)換成一個(gè)wrapper Object
(其實(shí)也是JS Object類型),返回給colorForWrod
。
如果我們在OC中調(diào)用這個(gè)colorForWrod
函數(shù),會(huì)是什么樣子呢?如下圖:
OC Caller
去調(diào)用這個(gè)colorForWrod
函數(shù),因?yàn)?code>colorForWrod函數(shù)接收的是一個(gè)String
類型那個(gè)參數(shù)word,OC Caller
傳過去的是一個(gè)NSString
類型的參數(shù),JSCore轉(zhuǎn)換成對應(yīng)的String
類型。然后colorForWrod
函數(shù)繼續(xù)向下調(diào)用,就像上面說的,知道其拿到返回的wrapper Object
,它將wrapper Object
返回給調(diào)用它的OC Caller
,JSCore又會(huì)在這時(shí)候把wrapper Object
轉(zhuǎn)成JSValue類型,最后再OC中通過對JSValue調(diào)用對應(yīng)的轉(zhuǎn)換方法,即可拿到里面包裝的值,這里我們調(diào)用- toObject
方法,最后會(huì)得到一個(gè)NSColor
對象,即從最開始那個(gè)暴露給JS的Block中返回的對象。
通過一步一步的分析,我們發(fā)現(xiàn),JavaScriptCore會(huì)在JS與OC交界處傳遞數(shù)據(jù)時(shí)做相應(yīng)的類型轉(zhuǎn)換,轉(zhuǎn)換規(guī)則如前面的OC-JS類型對照表。
3.1.1 使用 Block 的坑
使用Block暴露方法很方便,但是有2個(gè)坑需要注意一下:
- 不要在Block中直接使用JSValue
- 不要在Block中直接使用JSContext
因?yàn)锽lock會(huì)強(qiáng)引用它里面用到的外部變量,如果直接在Block中使用JSValue的話,那么這個(gè)JSvalue就會(huì)被這個(gè)Block強(qiáng)引用,而每個(gè)JSValue都是強(qiáng)引用著它所屬的那個(gè)JSContext的,這是前面說過的,而這個(gè)Block又是注入到這個(gè)Context中,所以這個(gè)Block會(huì)被context強(qiáng)引用,這樣會(huì)造成循環(huán)引用,導(dǎo)致內(nèi)存泄露。不能直接使用JSContext的原因同理。
那怎么辦呢,針對第一點(diǎn),建議把JSValue當(dāng)做參數(shù)傳到Block中,而不是直接在Block內(nèi)部使用,這樣Block就不會(huì)強(qiáng)引用JSValue了。
針對第二點(diǎn),可以使用[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 使用
舉個(gè)栗子,我們在Objective-C中有一個(gè)MyPoint類,它有兩個(gè)double類型的屬性,x,y,一個(gè)實(shí)例方法description 和一個(gè)類方法 makePointWithX: Y:
如果我們使用JSExport協(xié)議把這個(gè)類的對象暴露給JS,那么在JS中,我們怎么使用這個(gè)暴露過來的JS對象呢?他的屬性可以直接調(diào)用,就像調(diào)用JS對象的屬性一樣,他的實(shí)例方法也可以直接調(diào)用,就像調(diào)用JS對象中的方法一樣,然后他的類方法,也可以直接用某個(gè)全局對象直接調(diào)用。就像普通的JS一樣,但是操作的卻是一個(gè)OC對象。
實(shí)現(xiàn)這些只需要寫這樣一句話。
@protocol MyPointExports <JSExport>
聲明一個(gè)自定義的協(xié)議并繼承自JSExport協(xié)議。然后當(dāng)你把實(shí)現(xiàn)這個(gè)自定義協(xié)議的對象暴露給JS時(shí),JS就能像使用原生對象一樣使用OC對象了,也就是前面說的API目標(biāo)之高保真。
需要注意的是,OC中的函數(shù)聲明格式與JS中的不太一樣(應(yīng)該說和大部分語言都不一樣。。),OC函數(shù)中多個(gè)參數(shù)是用冒號(hào):
聲明的,這顯然不能直接暴露給JS調(diào)用,這不高保真。。
所以需要對帶參數(shù)的方法名做一些調(diào)整,當(dāng)我們暴露一個(gè)帶參數(shù)的OC方法給JS時(shí),JSCore會(huì)用以下兩個(gè)規(guī)則生成一個(gè)對應(yīng)的JS函數(shù):
- 移除所有的冒號(hào)
- 將跟在冒號(hào)后面的第一個(gè)小寫字母大寫
比如上面的那個(gè)類方法,轉(zhuǎn)換之前方法名應(yīng)該是 makePointWithX:y:
,在JS中生成的對應(yīng)的方法名就會(huì)變成 makePointWithXY
。
蘋果知道這種不一致可能會(huì)逼死某些強(qiáng)迫癥。。所以加了一個(gè)宏JSExportAs
來處理這種情況,它的作用是:給JSCore在JS中為OC方法生成的對應(yīng)方法指定名字。
比如,還是上面這個(gè)方法makePointWithX:y:
,可以這樣寫:
這個(gè)
makePoint
就是給JS中方法指定的名字,這樣,在JS中就能直接調(diào)用makePoint
來調(diào)用這個(gè)OC方法makePointWithX:y:
了。
注意:這個(gè)宏只對帶參數(shù)的OC方法有效。
然后,這里有一個(gè)JSExport協(xié)議使用的 小Demo 有興趣的可以看看,用起來其實(shí)挺簡單的。
3.2.3 探究
但是,光會(huì)用可不行,這個(gè)JSExoprt協(xié)議到底做了什么呢?
當(dāng)你聲明一個(gè)繼承自JSExport的自定義協(xié)議時(shí),就是在告訴JSCore,這個(gè)自定義協(xié)議中聲明的屬性,實(shí)例方法和類方法需要被暴露給JS使用。(不在這個(gè)協(xié)議中的方法不會(huì)被暴露出去。)
當(dāng)你把實(shí)現(xiàn)這個(gè)協(xié)議的類的對象暴露給JS時(shí),JS中會(huì)生成一個(gè)對應(yīng)的JS對象,然后,JSCore會(huì)按照這個(gè)協(xié)議中聲明的內(nèi)容,去遍歷實(shí)現(xiàn)這個(gè)協(xié)議的類,把協(xié)議中聲明的屬性,轉(zhuǎn)換成JS 對象中的屬性,實(shí)質(zhì)上是轉(zhuǎn)換成getter 和 setter 方法,轉(zhuǎn)換方法和之前說的block類似,創(chuàng)建一個(gè)JS方法包裝著OC中的方法,然后協(xié)議中聲明的實(shí)例方法,轉(zhuǎn)換成JS對象上的實(shí)例方法,類方法轉(zhuǎn)換成JS中某個(gè)全局對象上的方法。
那這里說的某個(gè)全局對象到底是什么呢?這涉及到JS中的知識(shí):
- Prototype & Constructor
在傳統(tǒng)的基于Class的語言如Java、C++中,繼承的本質(zhì)是擴(kuò)展一個(gè)已有的Class,并生成新的Subclass。但是,JS中是沒有class類型的,那JS種怎么實(shí)現(xiàn)繼承呢,答案就是通過原型對象(Prototype)。
JavaScript對每個(gè)創(chuàng)建的對象都會(huì)設(shè)置一個(gè)原型,指向它的原型對象。對象會(huì)從其原型對象上繼承屬性和方法。
當(dāng)我們用obj.xxx訪問一個(gè)對象的屬性時(shí),JavaScript引擎先在當(dāng)前對象上查找該屬性,如果沒有找到,就到其原型對象上找,如果還沒有找到,就一直上溯到object.prototype對象,最后,如果還沒有找到,就只能返回undefined。
原型對象也是一個(gè)對象,他有一個(gè)構(gòu)造函數(shù)Constructor,就是用來創(chuàng)建對象的。
假如我們有一個(gè)Student構(gòu)造函數(shù),然后用它創(chuàng)建了一個(gè)對象 xiaoming,那么小明這個(gè)對象的原型鏈就是這樣的。
function Student(name) {
this.name = name;
this.hello = function () {
alert('Hello, ' + this.name + '!');
}
}
var xiaoming = new Student('小明');
xiaoming ----> Student.prototype ----> Object.prototype ----> null
再詳細(xì)一點(diǎn)如下圖,xiaohong是Student函數(shù)構(gòu)造的另一個(gè)對象,紅色箭頭是原型鏈。注意,Student.prototype指向的對象就是xiaoming、xiaohong的原型對象,這個(gè)原型對象自己還有個(gè)屬性constructor,指向Student函數(shù)本身。
另外,函數(shù)Student恰好有個(gè)屬性prototype指向xiaoming、xiaohong的原型對象,但是xiaoming、xiaohong這些對象可沒有prototype這個(gè)屬性,不過可以用proto這個(gè)非標(biāo)準(zhǔn)用法來查看。
這樣我們就認(rèn)為xiaoming、xiaohong這些對象“繼承”自Student。
xiaoming的原型對象上又有一根紅色箭頭指向Object.prototype,這樣我們就說Student“繼承”自O(shè)bject。Object.prototype的原型對象又指向null。
更多關(guān)于Prototype和Constructor的知識(shí)可以看這里。
不知道有沒有覺得,這里的原型對象,有點(diǎn)像OC中的類,而構(gòu)造函數(shù),則有點(diǎn)像OC中的元類,OC中類方法都是放在元類當(dāng)中的,所以前面說的某個(gè)全局對象就是JS中的構(gòu)造函數(shù)。
這里我畫了一張圖,用來描述使用JSExport協(xié)議暴露對象時(shí),OC和JS中的對應(yīng)關(guān)系:
我們有一個(gè)MyPoint類的對象point,當(dāng)我們用JSExport協(xié)議將這個(gè)OC對象暴露給JS時(shí),JSCore首先會(huì)在JS上下文環(huán)境中為該類生成一個(gè)對應(yīng)的原型對象和構(gòu)造函數(shù),然后JSCore會(huì)掃描這個(gè)類,把其中在JSExport協(xié)議中聲明的內(nèi)容暴露給JS,屬性(即getter和setter方法)會(huì)被添加到原型對象上,而類方法會(huì)被添加到到這個(gè)構(gòu)造函數(shù)上,這個(gè)放的位置,就正好對應(yīng)了OC中的類和元類。
然后就像前面那張圖一樣,原型對象中有一個(gè)constructor屬性,指向構(gòu)造函數(shù),構(gòu)造函數(shù)中有一個(gè)prototype屬性指向原型對象。我們又知道,MyPoint類是繼承與NSObject類的,JSCore也會(huì)為暴露的類的父類創(chuàng)建原型對象和構(gòu)造函數(shù),NSObject類的原型對象就是JS中Object類的原型對象。
每一個(gè)原型對象都有一個(gè)屬性叫Prototype,大寫的P,他是一個(gè)指針,用來表示JS中的繼承關(guān)系(即原型鏈),MyPoint類的原型對象會(huì)指向NSObject類的原型對象。而NSObject的原型對象,及Object的原型對象會(huì)指向null。最后再用Mypoint類的構(gòu)造函數(shù)和原型對象在JS中去生成一個(gè)與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),不能自動(dòng)解決循環(huán)引用問題(retain cycle),需要程序員手動(dòng)處理,而JavaScript 用的是GC (準(zhǔn)確的說是 Tracing Garbage Collection),所有的引用都是強(qiáng)引用,但是垃圾回收器會(huì)幫我解決循環(huán)引用問題,JavaScriptCore 也一樣,一般來說,大多數(shù)時(shí)候不需要我們?nèi)ナ謩?dòng)管理內(nèi)存。
但是下面2種情況需要注意一下:
不要在JS中給OC對象增加成員變量,這句話的意思就是說,當(dāng)我們將一個(gè)OC對象暴露給JS后,就像前面說的使用JSExport協(xié)議,我們能想操縱JS對象一樣操縱OC對象,但是這時(shí)候,不要在JS中給這個(gè)OC對象添加成員變量,因?yàn)檫@個(gè)動(dòng)作產(chǎn)生的后果就是,只會(huì)在JS中為這個(gè)OC對象增加一個(gè)額外的成員變量,但是OC中并不會(huì)同步增加。所以說這樣做并沒有什么意義,還有可能造成一些奇怪的內(nèi)存管理問題。
-
OC對象不要直接強(qiáng)引用JSValue對象,這句話再說直白點(diǎn),就是不要直接將一個(gè)JSValue類型的對象當(dāng)成屬性或者成員變量保存在一個(gè)OC對象中,尤其是這個(gè)OC對象還暴露給JS的時(shí)候。這樣會(huì)造成循環(huán)引用。如下圖:
如何解決這個(gè)問題呢?你可能會(huì)想,不能強(qiáng)引用, 那就弱引用唄,就像圖上這樣,但是這樣做也是不行的,因?yàn)镴SValue沒用對象引用他,他就會(huì)被釋放了。
那怎么辦?分析一下,在這里,我們需要一種弱的引用關(guān)系,因?yàn)閺?qiáng)引用會(huì)造成循環(huán)引用,但是又不能讓這個(gè)JSValue因無人引用它而被釋放。簡而言之就是,弱引用但能保持JSValue不被釋放。
于是,蘋果退出了一種新的引用關(guān)系,叫conditional retain
,有條件的強(qiáng)引用,通過這種引用就能實(shí)現(xiàn)我們前面分析所需要的效果,而JSManagedValue
就是蘋果用來實(shí)現(xiàn)conditional retain的類。
4.1 JSManagedValue
這是JSManagedValue的一般使用步驟:
首先,用JSValue創(chuàng)建一個(gè)JSManagedValue對象,JSManagedValue里面其實(shí)就是包著一個(gè)JSValue對象,可以通過它里面一個(gè)只讀的value屬性取到,這一步其實(shí)是添加一個(gè)對JSValue的弱引用。
-
如果只有第一步,這個(gè)JSValue會(huì)在其對應(yīng)的JS值被垃圾回收器回收之后被釋放,這樣效果就和弱引用一樣,所以還需要加一步,在虛擬機(jī)上為這個(gè)JSManagedValue對象添加Owner(這個(gè)虛擬機(jī)就是給JS執(zhí)行提供資源的,待會(huì)再講),這樣做之后,就給JSValue增加一個(gè)強(qiáng)關(guān)系,只要以下兩點(diǎn)有一點(diǎn)成立,這個(gè)JSManagedValue里面包含的JSValue就不會(huì)被釋放:
- JSValue對應(yīng)的JS值沒有被垃圾回收器回收
- Owner對象沒有被釋放
這樣做,就即避免了循環(huán)引用,又保證了JSValue不會(huì)因?yàn)槿跻枚涣⒖提尫拧?/p>
五. 多線程
說多線程之前得先說下另一個(gè)類 JSVirtualMachine
, 它為JavaScript的運(yùn)行提供了底層資源,有自己獨(dú)立的堆棧以及垃圾回收機(jī)制。
JSVirtualMachine
還是JSContext的容器,可以包含若干個(gè)JSContext,在一個(gè)進(jìn)程中,你可以有多個(gè)JSVirtualMachine,里面包含著若干個(gè)JSContext,而JSContext中又有若干個(gè)JSValue,他們的包含關(guān)系如下圖:
需要注意的是,你可以在同一個(gè)JSVirtualMachine的不同JSContext中,互相傳遞JSValue,但是不能再不同的JSVirtualMachine中的JSContext之間傳遞JSValue。
這是因?yàn)?,每個(gè)JSVirtualMachine都有自己獨(dú)立的堆棧和垃圾回收器,一個(gè)JSVirtualMachine的垃圾回收器不知道怎么處理從另一個(gè)堆棧傳過來的值。
說回多線程,JavaScriptCore提供的API本身就是線程安全的。
你可以在不同的線程中,創(chuàng)建JSValue,用JSContext執(zhí)行JS語句,但是當(dāng)一個(gè)線程正在執(zhí)行JS語句時(shí),其他線程想要使用這個(gè)正在執(zhí)行JS語句的JSContext所屬的JSVirtualMachine就必須得等待,等待前前一個(gè)線程執(zhí)行完,才能使用這個(gè)JSVirtualMachine。
當(dāng)然,這個(gè)強(qiáng)制串行的粒度是JSVirtualMachine,如果你想要在不用線程中并發(fā)執(zhí)行JS代碼,可以為不同的線程創(chuàng)建不同JSVirtualMachine。
最后再補(bǔ)充一點(diǎn),就是關(guān)于如何獲取 UIWebView 中的 JSContext,由于篇幅問題這里就不贅述了,推薦一篇文章。
參考
Integrating JavaScript into Native Apps
Java?Script?Core API Reference
廖雪峰的JavaScript教程