還記得以前在網(wǎng)頁上用 iframe
指定一個 URL 并通過 native 的 - webView:shouldStartLoadWithRequest:navigationType:
回調(diào)方法攔截請求,再蛋疼的各種截取和判斷字符串,結(jié)合 json 結(jié)構(gòu)的字符串來獲取 web 端發(fā)來的數(shù)據(jù)。
通過 - stringByEvaluatingJavaScriptFromString:
來拼接老長的字符串來傳遞數(shù)據(jù)給 web 端嗎。。。
現(xiàn)在我們可以對這種方式說 「愛過~」 了。
先看一下 Objective-C 與 JavaScript 之間類型的對應(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
Function object
id
Wrapper object
Class
Constructor object
返回的 JSValue 轉(zhuǎn)換為 Objective-C 類型的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- ( BOOL ) toBool ;
- ( double ) toDouble ;
- ( int32_t ) toInt32 ;
- ( uint32_t ) toUInt32 ;
- ( NSNumber * ) toNumber ;
- ( NSString * ) toString ;
- ( NSDate * ) toDate ;
- ( NSArray * ) toArray ;
- ( NSDictionary * ) toDictionary ;
- ( id ) toObject ;
- ( id ) toObjectOfClass: ( Class ) expectedClass ;
我們主要使用的是 JSContext
這個類,下面通過幾個簡單的例子來演示。
首先需要加入 JavaScriptCore.framework 并引入。
1
#import <JavaScriptCore/JavaScriptCore.h>
Objective-C 調(diào)用純 JavaScript
例如有一段計算階乘的腳本
test.js 1
2
3
4
5
6
7
8
9
function factorial ( n ) {
if ( n < 0 ){
return ;
}
if ( n === 0 ){
return 1 ;
}
return n * factorial ( n - 1 )
};
將腳本文件拖入項目。
注意在項目的 TARGETS > Build Phases 中,把 *.js
文件從 Compile Sources 都拖到 Copy Bundle Resources,否則會有編譯警告。
通過 - evaluateScript:
方法把腳本引入 JSContext 對象
1
2
3
4
5
6
NSString * path = [[[ NSBundle mainBundle ] bundlePath ] stringByAppendingPathComponent: @"test.js" ];
NSString * testScript = [ NSString stringWithContentsOfFile: path encoding: NSUTF8StringEncoding error: nil ];
self . context = [[ JSContext alloc ] init ];
[ self . context evaluateScript: testScript ];
通過 - objectForKeyedSubscript:
傳入 function 的名稱以取到該方法, 再通過 - callWithArguments:
傳入?yún)?shù)并得到返回值(如果有)。
1
2
3
4
5
6
NSNumber * inputNumber = [ NSNumber numberWithInteger: [ self . inputNumberTextField . text integerValue ]];
JSValue * function = [ self . context objectForKeyedSubscript: @"factorial" ];
JSValue * result = [ function callWithArguments: @ [ inputNumber ]];
NSNumber * number = [ result toNumber ];
JavaScript 調(diào)用 native 代碼
首先通過一個協(xié)議來關(guān)聯(lián) JavaScript 的 function 和 native 方法,
還可通過 JSExportAs(functionName, Selector);
來指定 function 的別名。
1
2
3
4
5
6
7
8
9
10
11
12
@protocol TestJSExport < JSExport >
JSExportAs
( functionNameForJS /** JavaScript function 的別名 */ ,
- ( void ) handleFactorialCalculateWithNumber: ( NSNumber * ) number
);
- ( void ) pushToNextViewControllerWithTitle: ( NSString * ) title ;
@end
然后在 UIWebView
回調(diào)方法 - webViewDidFinishLoad:
中把 web 的 javaScriptContext 與我們用來操作的 JSContext
對象關(guān)聯(lián),并把 self
設(shè)為 JavaScript 調(diào)用 function 的對象。
1
2
3
4
5
self . context = [ webView valueForKeyPath: @"documentView.webView.mainFrame.javaScriptContext" ];
// 以 JSExport 協(xié)議關(guān)聯(lián) native 的方法
self . context [ @"native" ] = self ;
//此處有誤,關(guān)聯(lián)到 self 會造成 self 這個對象無法釋放的問題,建議使用后面的 block 形式,或者關(guān)聯(lián)到非 self 本身的對象。(weakSelf 也不行)
這樣在 web 中我們就可以通過所注冊的 native 對象來調(diào)用到 native 方法。
1
2
3
4
5
6
7
<textarea id= "input" style= "font-size:10pt;color:black;" ></textarea>
<input type= "button" value= "計算階乘" onclick= "native.functionNameForJS(input.value);" />
<a id= "push" href= "#" onclick= "native.pushToNextViewControllerWithTitle('Next VC');" >
push to next ViewController
</a>
建議使用后面的 block 形式,或者關(guān)聯(lián)到非 self 本身的對象。(weakSelf 也不行)
同樣的,先關(guān)聯(lián) javaScriptContext ,然后直接給 JSContext
對象 - setObject:forKeyedSubscript:
來設(shè)定相應(yīng) block。
1
2
3
4
5
self . context [ @"log" ] =
^ ( NSString * str )
{
NSLog ( @"%@" , str );
};
這樣在 web 中我們就可以直接調(diào)用上面注冊的 function
1
<input type= "button" value= "測試log" onclick= "log('測試');" />
使用 Block 要注意避免循環(huán)引用
給 JSContext
的 exceptionHandler
屬性賦予相應(yīng) Block
1
2
3
4
5
6
self . context . exceptionHandler =
^ ( JSContext * context , JSValue * exceptionValue )
{
context . exception = exceptionValue ;
NSLog ( @"%@" , exceptionValue );
};
我們可以來利用一些開源的 JavaScript 庫來做一些比 native 代碼更方便和易于實現(xiàn)的東西
隨便找了一個 Highcharts 圖表庫來做例子。
注意:Highcharts 開源但不完全免費。個人用戶及非商業(yè)用途免費,商業(yè)用途需要購買許可。
大致的調(diào)用代碼如下
1
2
3
4
5
6
7
8
9
NSArray * the1024Data = @ [ @ 33 , @ 41 , @ 32 , @ 51 , @ 42 , @ 103 , @ 136 ];
NSDictionary * the1024Dict = @ { @"name" : @"1024" , @"data" : the1024Data };
NSArray * theCCAVData = @ [ @ 8 , @ 11 , @ 21 , @ 13 , @ 20 , @ 52 , @ 43 ];
NSDictionary * theCCAVDict = @ { @"name" : @"CCAV" , @"data" : theCCAVData };
NSArray * seriesArray = @ [ the1024Dict , theCCAVDict ];
[ self . context [ @"drawChart" ] callWithArguments: @ [ seriesArray ]];
1
2
3
4
function drawChart ( seriesArray )
{
...
}
詳細(xì)的代碼就不貼了,見項目 JavaScriptCoreSample 。