JavaScriptCore和Objective-C

JavaScriptCore和Objective-C
144 作者 BobooO
2016.05.09 20:20 字?jǐn)?shù) 2140 閱讀 1700評論 8喜歡 54

在iOS開發(fā)中,因?yàn)镠5頁面的一些先天優(yōu)勢拆檬,原生界面里面摻雜著H5頁面是一種很常見的方案沟优。公司應(yīng)用最近因?yàn)闃I(yè)務(wù)需要一下子接入了大量H5界面慨菱,另外還要求:原生界面使用的是友盟統(tǒng)計分析,為了統(tǒng)計數(shù)據(jù)能在平臺連續(xù)、集中的展示出來,希望H5頁面的統(tǒng)計事件和原生界面的統(tǒng)計事件都上報到同一個后臺。為了滿足這個要求葵陵,就需要H5頁面使用友盟統(tǒng)計的iOS SDK來上報用戶事件,也就是說瞻佛,H5頁面需要與原生應(yīng)用進(jìn)行交互脱篙。
本來想從頭到尾把了解的方方面面都寫一下,但是后來在網(wǎng)上發(fā)現(xiàn)有很多優(yōu)秀的博客伤柄,所以就沒必要了绊困,這里簡單做一個歸納。
但是這些博客也存在一個共同的問題适刀,就是幾乎對交互過程中存在的問題和限制鮮有描述秤朗,所以我想本文略有價值的地方在于第二部分。
一笔喉、JavaScript和Objective-C的交互

交互實(shí)際上就是方法的互相調(diào)用取视,所以分兩部分。
(一)常挚、JS調(diào)用OC代碼
1作谭、攔截協(xié)議

JS調(diào)用OC代碼可以通過攔截NSRequest請求來調(diào)用原生方法進(jìn)行交互。
UIWebView的代理方法

  • (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

UIWebView每次加載請求內(nèi)容之前待侵,都會調(diào)用這個方法丢早,該方法返回YES/NO來決定UIWebView是否加載request請求姨裸。所以我們可以通過URL的協(xié)議頭甚至URL字符串來區(qū)別正常的URL請求和本地方法的調(diào)用請求秧倾。JS傳遞給OC的參數(shù)可以通過URL帶過來,如果參數(shù)內(nèi)容過長可以通過post請求來傳遞傀缩,本地在攔截request后那先,可以將HTTPBody中的請求內(nèi)容解析出來。
iOS6及以前赡艰,攔截協(xié)議是JS調(diào)用OC方法唯一的出路售淡,即使出現(xiàn)了一些第三方框架(比如WebViewJavaScriptBridge),也是基于攔截協(xié)議進(jìn)行的封裝慷垮。
iOS7及之后揖闸,攔截協(xié)議的方法仍然可用,但是蘋果給我提供了更友好料身、完善的方案汤纸。
2、使用JavaScriptCore

JavaScriptSore是蘋果在iOS7之后提供的一套框架芹血,它讓JS與OC的交互更加簡單方便贮泞。
要使用JavaScriptCore首先我們需要引入它的頭文件#import <JavaScriptCore/JavaScriptCore.h>
重要對象:

import "JSContext.h"

import "JSValue.h"

import "JSManagedValue.h"

import "JSVirtualMachine.h"

import "JSExport.h"

JSContext是JavaScript的運(yùn)行環(huán)境楞慈,他主要作用是執(zhí)行JS代碼和注冊O(shè)C方法接口,相當(dāng)于HTML中< JavaScript ></JavaScript >之間的內(nèi)容啃擦。
JSValue是JSContext的返回結(jié)果囊蓝,他對數(shù)據(jù)類型進(jìn)行了封裝,并且為JS和OC的數(shù)據(jù)類型之間的轉(zhuǎn)換提供了方法令蛉。
JSManagedValue是JSValue的封裝聚霜,用它可以解決JS和原生代碼之間循環(huán)引用的問題。
JSVirtualMachine 管理JS運(yùn)行時和管理JS暴露的OC對象的內(nèi)存言询。
JSExport是一個協(xié)議俯萎,通過實(shí)現(xiàn)它可以把一個OC對象暴漏給JS,這樣JS就可以調(diào)用這個對象暴露的方法运杭。
發(fā)現(xiàn)一個寫得很好的博客夫啊,做一次大自然的搬運(yùn)工,更詳細(xì)的內(nèi)容請參考 [iOS JavaScriptCore使用]

(二)辆憔、OC調(diào)用JS代碼
1撇眯、使用UIWebView的方法

  • (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script

2、 使用JavaScriptCore中JSContext的方法

  • (JSValue *)evaluateScript:(NSString *)script;
  • (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL *)sourceURL

具體使用可參考 [iOS JavaScriptCore使用]
二虱咧、使用JavaScriptCore遇到的坑
1熊榛、內(nèi)存泄漏問題

當(dāng)使用JSExport協(xié)議的方式來實(shí)現(xiàn)交互時,我們可能會在我們的交互對象中聲明了一個JSContext屬性用來保存JS上下文腕巡,代碼可能通常這樣

//聲明屬性
@property (nonatomic,strong) JSContext * context;

//使用
self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//或
self.context = [[JSContext alloc] init];

在注入JS的時候當(dāng)前JSContext上下文引用了當(dāng)前交互對象self玄坦,從而造成循環(huán)引用。
解決方法

使用Block進(jìn)行绘沉,不使用JSExport協(xié)議煎楣。
在交互對象與JSContext之間加一層代理。(處理過NSTimer循環(huán)引用問題的同學(xué)應(yīng)該熟悉這個方案)

2车伞、UIWebView加載第一個頁面JS調(diào)用本地方法正常择懂,但是頁面發(fā)生了跳轉(zhuǎn)后,JS調(diào)用本地方法就失效了

我們在代碼中注入JS代碼可能像這樣

//在- (void)viewDidLoad中注入

  • (void)viewDidLoad {
    [super viewDidLoad];
    NSURL *url = [NSURL URLWithString:urlString];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:request];

    //1另玖、使用block注入
    JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    context[@"stat"] = ^(NSString *event){

    };

    //2困曙、使用JSExport協(xié)議的方式注入一個對象
    Myobj *obj = [[Myobj alloc] init];
    self.jsContext[@"obj"] = obj;
    }

或者這樣

//在- (void)webViewDidFinishLoad:(UIWebView *)webView中注入

  • (void)webViewDidFinishLoad:(UIWebView *)webView
    {
    //1、使用block注入
    JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    context[@"stat"] = ^(NSString *event){

    };

    //2谦去、使用JSExport協(xié)議的方式注入一個對象
    Myobj *obj = [[Myobj alloc] init];
    self.jsContext[@"obj"] = obj;

這里我們要討論的是注入時機(jī)的問題慷丽。

當(dāng)我們在- (void)viewDidLoad中注入JS代碼之后,如果頁面發(fā)生了重定向鳄哭,此時web頁面的JS已經(jīng)發(fā)生了變化要糊,而- (void)viewDidLoad方法只會執(zhí)行一次,所以不再是之前我們注入過的那些JS了窃诉,此時再調(diào)用本地方法自然就失效了杨耙。

如果我們在- (void)webViewDidFinishLoad:(UIWebView *)webView方法中注入JS赤套,看起來貌似可以解決重定向之后調(diào)用失效的問題,因?yàn)閣ebView每次加載完成后都會回調(diào)- (void)webViewDidFinishLoad:(UIWebView *)webView珊膜,也就是說每次重定向之后容握,只要頁面加載完成,JS代碼就會重新被注入车柠。如果JS調(diào)用OC方法的時機(jī)是在頁面加載完成之后剔氏,比如點(diǎn)擊web界面上的按鈕或者由用戶手動觸發(fā)一個事件調(diào)用OC代碼,這種情況一定是web頁面加載完成之后才會發(fā)生的竹祷,而此時我們已經(jīng)重新注入了JS谈跛,這樣一點(diǎn)問題都沒有。但是塑陵,如果JS調(diào)用OC方法的時機(jī)剛好發(fā)生在頁面加載過程中呢尘分?比如web界面加載過程中自動執(zhí)行一些操作需要調(diào)用OC代碼师幕,而此時- (void)webViewDidFinishLoad:(UIWebView *)webView還沒有回調(diào)植酥,所以我們的JS代碼并沒有重新注入俭识,這里仍然會造成失效的問題。至于解決方案兼都,可以看這里 Why use JavaScriptCore in iOS7 if it can't access a UIWebView's runtime?嫂沉,貌似使用了私有API,有被拒的風(fēng)險啊~!

我們的應(yīng)用在統(tǒng)計H5頁面路徑的時候就是屬于需要JS自動調(diào)用OC方法的情況扮碧,當(dāng)用戶進(jìn)入頁面后需要讓JS調(diào)用OC方法上報一個統(tǒng)計事件趟章,上報這個事件時,僅僅是表示用戶進(jìn)入了這個界面慎王,并不跟用戶產(chǎn)生其他任何交互蚓土,所以明顯不能通過點(diǎn)擊一個按鈕來觸發(fā)。為了避開被拒的風(fēng)險柬祠,我是這樣做的

  • (void)webViewDidFinishLoad:(UIWebView *)webView
    {
    JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    context[@"stat"] = ^(NSString *event){
    [MobClick event:event];
    DDLogInfo(@"UMAnalytics...%@",event);
    };
    //頁面加載完成后北戏,手動觸發(fā)頁面跟蹤的統(tǒng)計事件
    [context evaluateScript:[NSString stringWithFormat:@"ios.start()"]];
    };

我將注入時機(jī)放在了- (void)webViewDidFinishLoad:(UIWebView *)webView中负芋,并與前端約定好上報H5頁面路徑的統(tǒng)計事件不再讓JS主動調(diào)用OC方法漫蛔,而是改為由我在頁面加載完成后被動觸發(fā),見上面最后一行代碼旧蛾。之所以這樣莽龟,一是避免web頁面重定向?qū)е路椒ㄊУ膯栴},二是頁面路徑的統(tǒng)計事件本來就應(yīng)該在界面顯示完成后再上報锨天,三是只需要知道狀態(tài)毯盈,不需要與用戶交互。這里我在本地觸發(fā)JS調(diào)用之后病袄,最終JS還是會調(diào)用由我注入的stat()方法搂赋,雖然饒了一個彎赘阀,但是H5頁面統(tǒng)計事件的埋點(diǎn)及其他邏輯就不再在OC中實(shí)現(xiàn)了,而是由H5自己去處理脑奠,做到讓H5像原生界面一樣上報統(tǒng)計事件基公。

iOS8引入了WKWebView代替了UIKit中的UIWebView,至于WKWebView與JavaScript的交互宋欺,玩法有比較大的變化轰豆,本文就先這樣。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末齿诞,一起剝皮案震驚了整個濱河市酸休,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌祷杈,老刑警劉巖斑司,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異但汞,居然都是意外死亡陡厘,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進(jìn)店門特占,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糙置,“玉大人,你說我怎么就攤上這事是目“梗” “怎么了?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵懊纳,是天一觀的道長揉抵。 經(jīng)常有香客問我,道長嗤疯,這世上最難降的妖魔是什么冤今? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮茂缚,結(jié)果婚禮上戏罢,老公的妹妹穿的比我還像新娘。我一直安慰自己脚囊,他們只是感情好龟糕,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著悔耘,像睡著了一般讲岁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天缓艳,我揣著相機(jī)與錄音校摩,去河邊找鬼。 笑死阶淘,一個胖子當(dāng)著我的面吹牛秧耗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播舶治,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼分井,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了霉猛?” 一聲冷哼從身側(cè)響起尺锚,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惜浅,沒想到半個月后瘫辩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坛悉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年伐厌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片裸影。...
    茶點(diǎn)故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡挣轨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出轩猩,到底是詐尸還是另有隱情卷扮,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布均践,位于F島的核電站晤锹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏彤委。R本人自食惡果不足惜鞭铆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望焦影。 院中可真熱鬧车遂,春花似錦、人聲如沸偷办。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽椒涯。三九已至,卻和暖如春回梧,著一層夾襖步出監(jiān)牢的瞬間废岂,已是汗流浹背祖搓。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留湖苞,地道東北人拯欧。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像财骨,于是被迫代替她去往敵國和親镐作。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 在iOS開發(fā)中隆箩,因?yàn)镠5頁面的一些先天優(yōu)勢该贾,原生界面里面摻雜著H5頁面是一種很常見的方案。公司應(yīng)用最近因?yàn)闃I(yè)務(wù)需要...
    BobooO閱讀 3,012評論 8 55
  • 跟原生開發(fā)相比捌臊,H5的開發(fā)相對來一個成熟的框架和團(tuán)隊(duì)來講在開發(fā)速度和開發(fā)效率上有著比原生很大的優(yōu)勢杨蛋,至少不用等待審...
    大沖哥閱讀 1,847評論 0 7
  • 隨著H5技術(shù)的興起,在iOS開發(fā)過程中理澎,難免會遇到原生應(yīng)用需要和H5頁面交互的問題逞力。其中會涉及方法調(diào)用及參數(shù)傳值等...
    Chris_js閱讀 3,088評論 1 8
  • 前言 Web 頁面中的 JS 與 iOS Native 如何交互是每個 iOS 猿必須掌握的技能。而說到 Nati...
    幽城88閱讀 2,208評論 1 8
  • 今兒放假 終于舒口氣 放假前 公司還有聚餐 我想休息下 撇開他們 獨(dú)自回來 手機(jī)電腦 齊轟炸 淘寶購物車裝滿 螞蟻...
    四毛may閱讀 323評論 0 0