最近跟同事聯(lián)調(diào)一個iOS與JS交互想接口,折騰了整整一天闸准,被迫重新研究了一下iOS與JS交互的原理霎奢,才發(fā)現(xiàn)原來項目中用的方案已經(jīng)是很老很老的方式了户誓,
效率不高,學習成本倒不低幕侠,但總歸之前用了很久都沒出現(xiàn)過什么問題帝美,所以還是簡單的總結(jié)下吧,然后就準備把這套東西淘汰掉用新的方式了橙依。
OC調(diào)用JS
OC調(diào)用JS的方法很簡單证舟,就是UIWebView的stringByEvaluatingJavaScriptFromString
方法硕旗,或者
WKWebView的- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
方法。
JS調(diào)用OC
其實現(xiàn)在項目中用的方案原理很簡單女责,就是攔截請求漆枚,然后獲取參數(shù)執(zhí)行OC的方法;
不過為了跟安卓統(tǒng)一抵知,并且方便復用墙基,做了一層封裝,給JS注入方法刷喜。
考慮到js有很多的全局函數(shù)残制,所以直接給js添加方法是不太合適的,可能會導致命名沖突掖疮,比較理想的做法是模塊化初茶,就是給js注入個對象,給這個對象再添加方法浊闪。
具體來說就是:
客戶端這里有一個bridge類恼布,里面有跟js端協(xié)商好的方法;
當網(wǎng)頁加載結(jié)束后(webViewDidFinishLoading
)搁宾,iOS這端會執(zhí)行一個方法折汞,取到bridge里面所有方法列表(用runtime方法獲取)盖腿,
然后拼裝成對應的js方法爽待,用evaluateJavaScript
方法注入給js,
js需要做的就是在需要的時候調(diào)用對應方法即可翩腐。
注意鸟款,js端除了判斷方法存在不存在和調(diào)用方法,其他什么都沒做栗菜,對象和所有的方法都是客戶端注入的欠雌。
所以這個方案的關(guān)鍵是拼接注入給js的方法,
這里用了一個巧妙的方法疙筹,即利用js可以執(zhí)行字符串中的JavaScript代碼的eval()
方法。
iOS端將bridge里獲取的方法列表拼接成字符串禁炒,跟js代碼的字符串拼在一起而咆,拼成一段JavaScript字符串,
然后用webView的evaluateJavaScript
方法調(diào)用就可以了
而注入給js的方法幕袱,就是讓js去加載一個指定格式的自定義的URL暴备,然后iOS端攔截這個請求,判斷如果是自定義的URL们豌,則去執(zhí)行自己的方法涯捻,
這里一般是提前一定好一個scheme浅妆,然后根據(jù)后面的字段來區(qū)別不同的方法,
這個方案js傳遞參數(shù)給OC也是很簡單障癌,直接拼在URL后面即可凌外。
理論上OC傳遞參數(shù)給js也是很簡單的,只需js端定義一個帶返回值的函數(shù)涛浙,然后iOS端要拼接這個函數(shù)的調(diào)用的字符串康辑,然后直接用webView的evaluateJavaScript
方法調(diào)用它就行了;
但是這跟上面“所有方法都是讓js去load一個自定義格式的URL”的設(shè)計是沖突的轿亮,
因為上面的設(shè)計是疮薇,所有方法都是iOS端注入的,注入的方法內(nèi)容是讓js去load一個自定義URL我注,而這個傳參的js方法不是去load一個自定義的URL按咒,而是去取一個OC執(zhí)行的js的結(jié)果,然后再進行其他操作但骨;
如何解決這個問題呢胖齐?
我自己想到的方法是,在注入方法的時候嗽冒,將注入的方法分為兩類呀伙,一類就是去load自定義URL的方法;另外一種是專門傳遞參數(shù)給js的方法添坊,也就是直接執(zhí)行拼接好的js函數(shù)的方法剿另。
我感覺這個方法是可行的,如果是一些簡單的傳值的方法的話贬蛙,所不定還挺高效雨女,但貌似有一個問題,執(zhí)行結(jié)果貌似是同步傳回給js的阳准,如果OC端執(zhí)行的方法比較耗時氛堕,那可能會卡住js端的進程。
前輩們在項目中使用了個更高級的辦法野蝇,就是調(diào)用js方法時讼稚,如果需要返回值的話,傳一個回調(diào)函數(shù)到OC绕沈∪裣耄回調(diào)函數(shù)固定以一個json字符串作為輸入?yún)?shù)。
為什么說這個方法高級呢乍狐,一是統(tǒng)一了注入給js的方法格式赠摇,而是統(tǒng)一了js注入方法的返回值的格式,三是統(tǒng)一了用異步回調(diào)。
需要注意的是:OC最終調(diào)用的方法只有一個參數(shù)藕帜,這個參數(shù)是一個js傳過來的參數(shù)數(shù)組烫罩,具體參數(shù)數(shù)組里面都是什么內(nèi)容,要在定義注入函數(shù)的時候就跟js端商量好洽故!
(這里還有一個小細節(jié)贝攒,如果沒有參數(shù),OC端的selector是沒有“:”的收津,如果有參數(shù)饿这,一定記得要拼接“:”,否則OC端會識別成不同的函數(shù)名)
回調(diào)函數(shù)還有一個需要注意的地方撞秋,因為OC回調(diào)的是js函數(shù)长捧,所以js端要在某個地方有保存這個函數(shù),直接在調(diào)用注入方法時實現(xiàn)這個函數(shù)貌似是不行的吻贿,必須要建立其調(diào)用js注入函數(shù)與callback函數(shù)之間的對應關(guān)系才行串结,否則oc調(diào)用js回調(diào)是不知道對應的回調(diào)函數(shù)是誰的。舅列。肌割。。帐要。這個比較繞
我又看了下js端同學些的代碼把敞,js端是在傳入callback的同時,將callback函數(shù)保存到一個數(shù)組里面榨惠,然后傳數(shù)組名稱和callback的index給OC奋早,所以O(shè)C端實際調(diào)用的是,拿到數(shù)組里保存的函數(shù)然后傳參數(shù)調(diào)用赠橙。
也就是說耽装,如果是需要OC傳遞參數(shù)給JS的方法,實際上不完全是OC這邊注入的期揪,還是需要JS端同學配合下的掉奄。
這樣看來,跟直接讓JS端同學實現(xiàn)幾個專門的傳參數(shù)方法代價也差不多凤薛。姓建。。
這個方案是UIWebView和WKWebView都可以使用的枉侧。
新的方式也就是iOS7之后的推出的JavaScriptCore引瀑,這個下一篇再寫。