現在的iOS項目中嵌入了越來越多的Web界面,當然是為了方便,那么為了迎合這一趨勢,作為iOS開發程序員,我們必須要了解怎么樣用OC去和這些Web界面進行交互。這里介紹的是JavaScriptCore這個框架,他就是蘋果為了解決這一問題而推出的框架。
JavaScriptCore的類說明
在做OC與H5的交互之前,我們需要先導入JavaScriptCore框架
#import <JavaScriptCore/JavaScriptCore.h>
然后我們進入到JavaScriptCore框架的JavaScriptCore.h,我們發現總共有如下幾個類。
#if defined(__OBJC__) && JSC_OBJC_API_ENABLED #import "JSContext.h" #import "JSValue.h" #import "JSManagedValue.h" #import "JSVirtualMachine.h" #import "JSExport.h" #endif
1. JSContext
JSContext 代表一個JavaScript的執行環境的一個實例。所有的JavaScript執行都是在上下文內。作為上下文,我們在很多情況下遇到過,比如CoreData,CoreGraphics等等,那么上下文對象到底是什么呢?你可以理解為是一個兩者的連接橋梁,如圖所示.
2.JSValue
JSValue的主要作用就是用來接收JSContext執行后的返回結果,當然了,JSValue可以是Js的任意類型。例如,JS中的變量,對象,以及函數。下面,我們舉個例子來說明一下,例如我們現在需要從JSContext對象中接收到一個變量,我們可以如下所示。
首先在<script>標簽中我們定義了一個字符串變量
<script type="text/javascript"> var myObject = "myObject"; </script>
然后,我們在OC的代碼中如下表示,其中self.context代表着已經初始化完成的JSContext對象。
JSContext 對象的創建有兩種方法
/*使用這個方法進行初始化 系統會自動創建一個JSVirtualMachine對象 然后 調用下面的方法 */
- (instancetype)init; /*! JSVirtualMachine對象其實代表着一個JavaScript對象空間或者一組執行資源。初始化只需要init 就行了 */ - (instancetype)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;
當然 如果你使用了WebView 你可以這樣初始化一個JSContext對象
JSContext *context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
有了JSContext,我們只需要在OC中寫出下面的代碼,就可以拿到我們在JS中定義的變量了
JSValue *value = self.context[@"myObject"]; //轉換為OC的字符串 NSLog(@"%@",[value toString]);
3.JSManagedValue
JSManagedValue這個類其實到現在我還沒有具體用到過.當時這里我也要根據API文檔說明一下.JSManagedValue是JSValue的封裝,用它可以解決JS和OC代碼之間循環引用的問題,JSManagedValue最常用的用法就是安全的從內存堆區里面引用JSValue對象.如果JSValue存儲在內存的堆區的方式是不正確的,很容易造成循環引用,然后導致JSContext對象不能正確的釋放掉.
4.JSValueMachine
一個JSVirtualMachine對象其實代表着一個JavaScript對象空間或者一組執行資源。JSVirtualMachine支持線程安全鎖,虛擬機,並發分配支持的JavaScript執行.也就說JSVirtualMachine用來管理整個JavaScript,當然了,這個類我也沒有用到過.
5.JSExport
JSExport是一個協議,通過實現它可以完成把一個native對象暴漏給JS,當然了我們要指定native對象.比如我們指定native對象就是自身.如下所示.
self.context[@"native"] = self;
JavaScript 調取 OC的代碼
那么我們怎么使用JavaScriptCore 進行JS與OC的交互呢,我們先來介紹JS調取OC的代碼的方法。主要有兩種方法:一種是很簡單的直接使用Block,另一種是使用JSExport協議。
Block方式
block的方式比較簡單,也是我比較推薦的一種,但是要注意防止循環引用問題。首先 我們說一下不帶參數的函數調用,也就是我們不需要從網頁那邊有參數的傳值。比如,頁面跳轉等等,代碼如下:
HTML文件中的<body>標簽中的按鈕代碼
<button type="button" onclick="buttonClick()">點擊按鈕返回上一個頁面</button>
OC中在- (void)webViewDidFinishLoad:(UIWebView *)webView方法中對block塊進行代碼實現.
__weak typeof(self)weakSelf = self; self.context[@"buttonClick"] = ^() { [weakSelf.navigationController popViewControllerAnimated:YES]; };
這樣當你點擊Web頁面上的按鈕的時候,就會執行block中的代碼。進行頁面跳轉。
接下來 我們看一下,通過頁面的傳值,我們把H5標簽的之作為參數進行傳值操作。並且調用OC的block進行打印。
HTML文件中的<body>標簽中的代碼
<body bgcolor="antiquewhite"> <!--<!–打開本地 或者網頁–>--> <!--<a href="news.html" target="_top">打開本地</a>--> <!--<h1 align="center">標題一</h1>--> <!--<h2 class="h2ID">標題2</h2>--> <button type="button" onclick="buttonClick('你好','世界')">點擊按鈕返回上一個頁面</button> <script type="text/javascript"> function buttonClick(str1,str2) { alert(str1 + "---" + str2); } </script> </body>
OC調用這個block中的代碼 有兩種方法
- (void)webViewDidFinishLoad:(UIWebView *)webView { self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; //第一種方法 // self.context[@"buttonClick"] = ^() { // //獲取該方法的所有參數 // NSArray *args = [JSContext currentArguments]; // for (JSValue *arg in args) { // NSLog(@"%@",arg); // } // // }; //第二種方法 self.context[@"buttonClick"] = ^(NSString *str1,NSString *str2) { NSLog(@"%@ and %@",str1,str2); }; }
點擊按鈕 可以看到 打印結果 你好 世界
JSExport 協議方式
通過實現JSExport協議方式進行OC與JS的交互,這里我只是簡單的實現以下沒有參數的函數調用.首先,我們在HTML文件中創建一個按鈕用來調用OC中JSExport協議方法.
聲明一個person類
.h #import <Foundation/Foundation.h> #import <JavaScriptCore/JavaScriptCore.h> @protocol webJsExport <JSExport> - (void) myButtonClick; @end @interface Person : NSObject<webJsExport> @end .m #import "Person.h" @implementation Person - (void)myButtonClick { NSLog(@"再來一次"); } @end
HTML
<button type="button" onclick="stu.myButtonClick()">JSExport沒有參數</button>
調用
- (void)viewDidLoad { [super viewDidLoad]; self.p = [[Person alloc] init]; self.webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)]; self.webView.delegate = self; NSURL *baseURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; NSString *path = [[NSBundle mainBundle] pathForResource:@"Index" ofType:@"html"]; NSString *html = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]; [self.webView loadHTMLString:html baseURL:baseURL]; [self.view addSubview:self.webView]; // self.context = [[JSContext alloc] init]; // JSValue *value = self.context[@"myObject"]; // //轉換為OC的字符串 // NSLog(@"%@",[value toString]); // __weak typeof(self)weakSelf = self; // self.context[@"buttonClick"] = ^() { // [weakSelf.navigationController popViewControllerAnimated:YES]; // }; } - (void)webViewDidFinishLoad:(UIWebView *)webView { self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; //第一種方法 // self.context[@"buttonClick"] = ^() { // //獲取該方法的所有參數 // NSArray *args = [JSContext currentArguments]; // for (JSValue *arg in args) { // NSLog(@"%@",arg); // } // // }; //第二種方法 self.context[@"buttonClick"] = ^(NSString *str1,NSString *str2) { NSLog(@"%@ and %@",str1,str2); }; self.context[@"stu"] = self.p; }
點擊JSExport沒有參數 會打印:在來一次
OC調用JavaScript函數
今天我們說一下如何使用JavaScriptCore讓OC調用JavaScript函數,使用JavaScriptCore進行OC調用JavaScript函數是很容易進行傳值操作的.首先我們需要在HTML文件創建愛你一個帶有id的標簽以及一個JavaScript函數.代碼如下所示.
HTML代碼
<b id="label">需要改變的標簽</b> <script type="text/javascript"> function labelAction(obj) { document.getElementById('label').innerHTML = obj; } </script>
然后 就是OC中的代碼了
下面就是實現方法,首先我們要先通過JSContext對象獲取到JS中的對應函數並且使用JSValue對象進行接收.然后我們通過使用- (JSValue *)callWithArguments:(NSArray *)arguments;進行JS函數的調用,當然了這里的JS函數沒有返回值,我也就沒有做接收返回值的工作. -(void)changeWebTxet{ JSValue *labelAction = self.context[@"labelAction"]; [labelAction callWithArguments:@[@"你好,JS世界!"]]; }
通過上面的介紹,我們可以看出來 使用JavaScriptCore 來進行OC與H5 的交互是很方便的,比以前通過WebView的一些方法交互方便多了。