一:webview:互調(diào)底層用的JavaScriptCore
js調(diào)用oc方法,然后把方法結(jié)果返回給js
例如:用戶在網(wǎng)頁做了某個點(diǎn)擊操作,網(wǎng)頁需要從客戶端獲取該用戶信息(用戶ID拨匆,設(shè)備ID,系統(tǒng)版本等)
1、客戶端和 web端定義好統(tǒng)一的偽協(xié)議
例如需要獲取用戶信息:tcan://getUserInfo?callback=window.getUserInfoResult
1.1藤巢、tcan://只是定義的一個前綴,可以用不一樣的前綴來區(qū)分不同的模塊(看業(yè)務(wù)需求來定息罗,也可以不區(qū)分)
1.2掂咒、getUserInfo 用來區(qū)分不同的操作
1.3、 callback=window.getUserInfoResult 表明可通過callback來獲取需要執(zhí)行的js方法名
2、js請求 oc:
在網(wǎng)頁點(diǎn)擊等行為的時候绍刮,js操作跳轉(zhuǎn):
元素.onclick = function( ) {
window.location.href = ‘tcan://getUserInfo?callback=window.getUserInfoResult';
}
3温圆、oc響應(yīng)請求并按需調(diào)用 js:stringByEvaluatingJavaScriptFromString
3.1、客戶端在UIWebViewDelegate方法 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
中攔截加載孩革。
//獲取加載的URL:
NSURL *requestURL = request.URL;
//判斷頭部廉油,區(qū)分不同模塊的邏輯:
if ([requestURL.absoluteString hasPrefix:@"tcan://"]) {
//獲取host
NSString *host = requestURL.host;
//根據(jù)不同的host做不同的響應(yīng)
if ([host isEqualToString:@"getUserInfo"]) {
//獲取js回調(diào)函數(shù)名
NSString *jsCallbackFunc = [parseObject.url parameterForKey:@"callback”];
if (![jsCallbackFunc isUndefine]) {
//設(shè)置要給js傳的參數(shù)
NSDictionary *postParameter = [NSDictionary dictionaryWithObjectsAndKeys:@"用戶ID", @"user_id",
@"設(shè)備ID", @"uid", nil];
//字典轉(zhuǎn)成json字符串(這里用了JSONKit),可以對數(shù)據(jù)進(jìn)行進(jìn)一步加鹽加密寞焙,例如AES-256
NSString *postParameterString = [postParameter JSONString];
//拼接js函數(shù)
NSString *js_excmd = [NSString stringWithFormat:@"%@('%@')", jsCallbackFunc, postParameterString];
//執(zhí)行
[webview stringByEvaluatingJavaScriptFromString:js_excmd];
}
}else if([host isEqualToString:@“xxx"]) {
}else {
}
}else if (xxx) {
//另一套邏輯
}
二:蘋果原生API:JavaScriptCore (iOS7.0+ 使用)
主要的兩個類:
- JSContext: JS上下文(運(yùn)行環(huán)境)吕粗,可用對象方法去執(zhí)行JS代碼(evaluateScript),可通過上下文對象去獲取JS里的數(shù)據(jù)(上下文對象[key])饱搏,并用JSValue對象接收
- JSValue: 用于接收J(rèn)SContext對象獲取的數(shù)據(jù)非剃,可以是任意對象,方法推沸。
注:JSValue對其對應(yīng)的JS值和其所屬的JSContext對象都是強(qiáng)引用的關(guān)系备绽。因?yàn)镴SValue需要這兩個東西來執(zhí)行JS代碼,所以JSValue會一直持有著它們鬓催。
所以肺素,在用block時,需考慮循環(huán)引用問題
1宇驾、不要在Block中直接使用JSValue:建議把JSValue當(dāng)做參數(shù)傳到Block中倍靡,而不是直接在Block內(nèi)部使用,這樣Block就不會強(qiáng)引用JSValue了
2飞苇、不要在Block中直接使用JSContext:可以使用[JSContext currentContext] 方法來獲取當(dāng)前的Context
類型對應(yīng)關(guān)系:
Objective-C type | JavaScript type
--------------------+---------------------
nil | undefined
NSNull | null
NSString | string
NSNumber | number, boolean
NSDictionary | Object object
NSArray | Array object
NSDate | Date object
NSBlock (1) | Function object (1)
id (2) | Wrapper object (2)
Class (3) | Constructor object (3)
//------------------OC調(diào)用JS------------------//
oc獲取js變量菌瘫,注:js代碼要先被執(zhí)行,才能通過上下文獲取
- (void)ocGetJSVar
{
//定義JS代碼
NSString *jsCode = @"var a = 'a'";
//創(chuàng)建JS運(yùn)行環(huán)境
JSContext *ctx = [[JSContext alloc] init];
//!!!:執(zhí)行JS代碼---先執(zhí)行布卡,后面才能獲取
[ctx evaluateScript:jsCode];
//獲取變量
JSValue *value = ctx[**@“a"**];
//JSValue轉(zhuǎn)NSString
NSString *valueStr = value.toString;
//打印結(jié)果:a
NSLog(@"%@",valueStr);
}
oc調(diào)用js方法雨让,并獲取返回結(jié)果
- (void)ocCallJSFunc
{
NSString *jsCode = @"function say(str){"
" return str; "
"}";
// 創(chuàng)建JS運(yùn)行環(huán)境
JSContext *ctx = [[JSContext alloc] init];
// 執(zhí)行JS代碼
[ctx evaluateScript:jsCode];
//!!!:執(zhí)行JS代碼---先執(zhí)行,后面才能獲取
JSValue *say = ctx[@"say"];
// OC調(diào)用JS方法忿等,獲取方法返回值
JSValue *result = [say callWithArguments:@[@"hello world!"]];
// 打印結(jié)果:hello world!
NSLog(@"%@",result);
}
//------------------JS調(diào)用OC—————————//
1栖忠、block方式:
用block定義js函數(shù),并執(zhí)行(OC調(diào)用執(zhí)行js贸街,而js是調(diào)用的oc block)
- (void)jsCallOCBlock
{
JSContext *ctx = [[JSContext alloc] init];
//OC中NSBlock對應(yīng)js中Function object
ctx[@"goto"] = ^(NSString *parmStr){
//block內(nèi)不要直接使用ctx庵寞,會循環(huán)引用(ctx已經(jīng)引用block),若外部有JSValue薛匪,也不能在block內(nèi)直接調(diào)用(JSValue強(qiáng)持有了ctx )
//獲取JS調(diào)用參數(shù)
NSLog(@"parmStr:%@",parmStr);
//可以直接獲取所有參數(shù)
NSArray *arguments = [JSContext currentArguments];
NSLog(@"%@",arguments[0]);
};
//JS執(zhí)行代碼,調(diào)用goto方法捐川,并傳入?yún)?shù)school
NSString *jsCode = @"goto('school')";
//執(zhí)行
[ctx evaluateScript:jsCode];
}
2、JSExport 協(xié)議
需要在JS中生成OC對應(yīng)的類逸尖,然后再通過JS調(diào)用古沥。
方法:
通過自定義一個遵循JSExport的協(xié)議瘸右,把需要被JS訪問的OC類中的屬性,方法暴露給JS使用
步驟:
1岩齿、自定義協(xié)議太颤,協(xié)議遵循JSExport的協(xié)議,協(xié)議中的屬性和方法就是OC對象要暴露給JS盹沈,讓JS可以直接調(diào)用的
例如:
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
@protocol PersonJSExport <JSExport>
@property (nonatomic, strong) NSString *address;
//無參數(shù)方法
- (void)play;
//因?yàn)镴S函數(shù)命名規(guī)則和OC規(guī)則不一樣龄章,所以當(dāng)有多個參數(shù)時,可以使用OC提供了一個宏*JSExportAs*乞封,指定JS應(yīng)該生成什么樣的函數(shù)來對應(yīng)OC的方法做裙。
//不使用JSExportAs指定關(guān)聯(lián)也可以正常調(diào)用,后面直接接多個參數(shù)
//多參數(shù)方法
//不指定關(guān)聯(lián)
- (void)play:(NSString *)address time:(NSString *)time;
//指定關(guān)聯(lián)
JSExportAs(gotoSchool, - (void)goToSchoolWithSchoolName:(NSString *)name address:(NSString *)address);
@end
2肃晚、自定義一個遵循第一步創(chuàng)建的協(xié)議的OC對象菇用,實(shí)現(xiàn)協(xié)議的方法
例如:Person對象
3、JS調(diào)用對象協(xié)議聲明的方法
- (void)jsCallOCClass
{
// 創(chuàng)建Person對象,Person對象必須遵守JSExport協(xié)議
Person *p = [[Person alloc] init];
p.address = @"aaa";
JSContext *ctx = [[JSContext alloc] init];
// 在JS中生成Person對象person陷揪,并且擁有p內(nèi)部的值
ctx[@"person"] = p;
// 執(zhí)行JS代碼
//NSString *jsCode = @"person.play()";
NSString *jsCode = @"person.play('北京天安門','now')";
//NSString *jsCode = @"person.gotoSchool('實(shí)驗(yàn)中學(xué)','廣州')";
[ctx evaluateScript:jsCode];
}
另外,若要調(diào)用OC系統(tǒng)的類杂穷,例如UIView
需要同樣創(chuàng)建協(xié)議悍缠,只是在第三步用runtime給系統(tǒng)類UIView添加創(chuàng)建的協(xié)議
class_addProtocol([UIView class], @protocol(UIViewlJSExport));
JavaScriptCore參考:深入淺出 JavaScriptCore
三:第三方框架:WebViewJavaScripteBridge