你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> JS&iOS原生交互

JS&iOS原生交互

編輯:IOS開發綜合

關於原生和hybid之爭,這裡不做探討.主要講講JS和OC交互

OC執行JS代碼

1.stringByEvaluatingJavaScriptFromString
這個方法是UIWebView裡面的方法,也是最為簡單的和JS交互的方式
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

用法比較簡單,一般在代理方法- (void)webViewDidFinishLoad:(UIWebView*)webView中使用

// 獲取當前頁面的title
NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"];

// 獲取當前頁面的url
NSString *url = [webview stringByEvaluatingJavaScriptFromString:@"document.location.href"];

OC執行JS&JS執行OC

1.JavaScriptCore
這個是iOS7以後引進的,使用起來可簡單,也可以比較復雜.
熟悉一下裡面常見得幾個對象及協議
JSContext:給JavaScript提供運行的上下文環境,通過-evaluateScript:方法就可以執行一JS代碼
JSValue:JavaScript和Objective-C數據和方法的橋梁,封裝了JS與ObjC中的對應的類型,以及調用JS的API等
JSManagedValue:管理數據和方法的類
JSVirtualMachine:處理線程相關,使用較少
JSExport:這是一個協議,如果采用協議的方法交互,自己定義的協議必須遵守此協議

對象簡介


簡單方式:直接調用JS代碼

// 一個JSContext對象,就類似於Js中的window,
// 只需要創建一次即可。
self.jsContext = [[JSContext alloc] init];

//  jscontext可以直接執行JS代碼。
[self.jsContext evaluateScript:@"var num = 10"];
[self.jsContext evaluateScript:@"var squareFunc = function(value) { return value * 2 }"];
// 計算正方形的面積
JSValue *square = [self.jsContext evaluateScript:@"squareFunc(num)"];

// 也可以通過下標的方式獲取到方法
JSValue *squareFunc = self.jsContext[@"squareFunc"];
JSValue *value = [squareFunc callWithArguments:@[@"20"]];
NSLog(@"%@", square.toNumber);
NSLog(@"%@", value.toNumber);

快速調用Block,可傳參數
各種數據類型可以轉換,Objective-C的Block也可以傳入JSContext中當做JavaScript的方法使用。雖然JavaScritpCore沒有自帶(畢竟不是在網頁上運行的,自然不會有window、document、console這些類了),仍然可以定義一個Block方法來調用NSLog來模擬:

JSContext *context = [[JSContext alloc] init];
context[@"log"] = ^() {
NSLog(@"+++++++Begin Log+++++++");
NSArray *args = [JSContext currentArguments];
for (JSValue *jsVal in args) {
NSLog(@"%@", jsVal);
}
JSValue *this = [JSContext currentThis];
NSLog(@"this: %@",this);
NSLog(@"-------End Log-------");
};
[context evaluateScript:@"log('ider', [7, 21], { hello:'world', js:100 });"];
//
// Output:
// +++++++Begin Log+++++++
// ider
// 7,21
// [object Object]
// this: [object GlobalObject]
// -------End Log-------

通過Block成功的在JavaScript調用方法回到了Objective-C,而且依然遵循JavaScript方法的各種特點,比如方法參數不固定。也因為這樣,JSContext提供了類方法來獲取參數列表(+ (JSContext *) currentArguments;)和當前調用該方法的對象(+ (JSValue *)currentThis)。對於"this"
,輸出的內容是GlobalObject,這也是JSContext對象方法- (JSValue *)globalObject;
所返回的內容。因為我們知道在JavaScript裡,所有全局變量和方法其實都是一個全局變量的屬性,在浏覽器中是window,在JavaScriptCore是什麼就不得而知了。Block可以傳入JSContext作方法,但是JSValue沒有toBlock方法來把JavaScript方法變成Block在Objetive-C中使用。畢竟Block的參數個數和類型已經返回類型都是固定的。雖然不能把方法提取出來,但是JSValue提供了- (JSValue *)callWithArguments:(NSArray *)arguments;
方法可以反過來將參數傳進去來調用方法。

JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"function add(a, b) { return a + b; }"];
JSValue *add = context[@"add"];
NSLog(@"Func: %@", add);
JSValue *sum = [add callWithArguments:@[@(7), @(21)]];
NSLog(@"Sum: %d",[sum toInt32]);
// OutPut:
// Func: function add(a, b) { return a + b; }
// Sum: 28
JSValue
還提供- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;
讓我們可以直接簡單地調用對象上的方法。只是如果定義的方法是全局函數,那麼很顯然應該在JSContext的globalObject對象上調用該方法;如果是某JavaScript對象上的方法,就應該用相應的JSValue

對象調用。
異常處理Objective-C的異常會在運行時被Xcode捕獲,而在JSContext中執行的JavaScript如果出現異常,只會被JSContext捕獲並存儲在exception屬性上,而不會向外拋出。時時刻刻檢查JSContext
對象的exception是否不為nil顯然是不合適,更合理的方式是給JSContext
對象設置exceptionHandler,它接受的是^(JSContext context, JSValue exceptionValue)
形式的Block。其默認值就是將傳入的exceptionValue賦給傳入的context的exception屬性:

^(JSContext *context, JSValue *exceptionValue) {
context.exception = exceptionValue;
};

我們也可以給exceptionHandler
賦予新的Block以便在JavaScript運行發生異常的時候我們可以立即知道:

JSContext *context = [[JSContext alloc] init];
context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
NSLog(@"%@", exception);
con.exception = exception;
};
[context evaluateScript:@"ider.zheng = 21"];
//Output:
// ReferenceError: Can't find variable: ider
使用Block的注意事項
從之前的例子和介紹應該有體會到Block在JavaScriptCore中起到的強大作用,它在JavaScript和Objective-C之間的轉換 建立起更多的橋梁,讓互通更方便。但是要注意的是無論是把Block傳給JSContext對象讓其變成JavaScript方法,還是把它賦給exceptionHandler屬性,在Block內都不要直接使用其外部定義的JSContext對象或者JSValue,應該將其當做參數傳入到Block中,或者通過JSContext的類方法+ (JSContext )currentContext;來獲得。否則會造成循環引用使得內存無法被正確釋放。比如上邊自定義異常處理方法,就是賦給傳入JSContext對象con,而不是其外創建的context對象,雖然它們其實是同一個對象。這是因為Block會對內部使用的在外部定義創建的對象做強引用,而JSContext也會對被賦予的Block做強引用,這樣它們之間就形成了循環引用使得內存無法正常釋放。對於JSValue也不能直接從外部引用到Block中,因為每個JSValue上都有JSContext
的引用 (@property(readonly, retain) JSContext
context;),JSContext再引用Block同樣也會形成引用循環。
前面十分的簡單方便而且高效,不過也僅限於數值型、布爾型、字符串、數組等這些基礎類型。
為了方便起見,以下所有代碼中的JSContext對象都會添加如下的log
方法和eventHandler
JSContext *context = [[JSContext alloc] init];
context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
NSLog(@"%@", exception);
con.exception = exception;
};
context[@"log"] = ^() {
NSArray *args = [JSContext currentArguments];
for (id obj in args) {
NSLog(@"%@",obj);
}
};

復雜但強大的方式:通過協議,模型實現(事先和前端協商格式)
首先,我們需要先定義一個協議,而且這個協議必須要遵守JSExport協議。
注意js中調用oc方法時參數的寫法

@protocol JavaScriptObjectiveCDelegate 

// JS調用此方法來調用OC的系統相冊方法
- (void)callSystemCamera;

// 在JS中調用時,函數名應該為showAlertMsg(arg1, arg2)
// 這裡是只兩個參數的。
- (void)showAlert:(NSString *)title msg:(NSString *)msg;

// 通過JSON傳過來
- (void)callWithDict:(NSDictionary *)params;

// JS調用Oc,然後在OC中通過調用JS方法來傳值給JS。
- (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary *)params;

@end

為了在調用原生方法之後調用對應的JS方法,寫兩個JS方法:

 var jsFunc = function() {
   alert('Objective-C call js to show alert');
 }

 var jsParamFunc = function(argument) {
   document.getElementById('jsParamFuncSpan').innerHTML
   = argument['name'];
 }

接下來,我們還需要定義一個模型,實現了上面定義協議裡面的方法:

// 此模型用於注入JS的模型,這樣就可以通過模型來調用方法。
@interface ObjCModel : NSObject 

@property (nonatomic, weak) JSContext *jsContext;
@property (nonatomic, weak) UIWebView *webView;

@end

實現這個模型:

@implementation ObjCModel

- (void)callWithDict:(NSDictionary *)params {
 NSLog(@"Js調用了OC的方法,參數為:%@", params);
}

// Js調用了callSystemCamera
- (void)callSystemCamera {
 NSLog(@"JS調用了OC的方法,調起系統相冊");

 // JS調用後OC後,又通過OC調用JS,但是這個是沒有傳參數的
 JSValue *jsFunc = self.jsContext[@"jsFunc"];
 [jsFunc callWithArguments:nil];
}

- (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary *)params {
 NSLog(@"jsCallObjcAndObjcCallJsWithDict was called, params is %@", params);

 // 調用JS的方法
 JSValue *jsParamFunc = self.jsContext[@"jsParamFunc"];
 [jsParamFunc callWithArguments:@[@{@"age": @10, @"name": @"lili", @"height": @158}]];
}

- (void)showAlert:(NSString *)title msg:(NSString *)msg {
 dispatch_async(dispatch_get_main_queue(), ^{
   UIAlertView *a = [[UIAlertView alloc] initWithTitle:title message:msg delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];
   [a show];
 });
}

@end

接下來,我們在controller中在webview加載完成的代理中,給JS注入模型。

注意,獲取webview的jsContext的方法 self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
 self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

 // 通過模型調用方法,這種方式更好些。
 ObjCModel *model  = [[ObjCModel alloc] init];
 self.jsContext[@"OCModel"] = model;
 model.jsContext = self.jsContext;
 model.webView = self.webView;

 self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
   context.exception = exceptionValue;
   NSLog(@"異常信息:%@", exceptionValue);
 };
}

通過KVC取得context,其路徑為documentView.webView.mainFrame.javaScriptContext。這樣就可以獲取到JS的context,然後為這個context注入我們的模型對象。

這裡我們定義了兩個JS方法,一個是jsFunc,不帶參數。
另一個是jsParamFunc,帶一個參數。

接下來,我們在html中的body中添加以下代碼:



Test how to use objective-c call js

 

現在就可以測試代碼了。

當我們點擊第一個按鈕:Call ObjC system camera時,
通過OCModel.callSystemCamera(),就可以在HTML中通過JS調用OC的方法。
在OC代碼中,我們的callSystemCamera方法體中,添加了以下兩行代碼,就是獲取HTML中所定義的JS就去jsFunc,然後調用它。

 JSValue *jsFunc = self.jsContext[@"jsFunc"];
 [jsFunc callWithArguments:nil];

這樣就可以在JS調用OC方法時,也讓OC反饋給JS。

看看下面傳字典參數:

- (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary *)params {
 NSLog(@"jsCallObjcAndObjcCallJsWithDict was called, params is %@", params);

 // 調用JS的方法
 JSValue *jsParamFunc = self.jsContext[@"jsParamFunc"];
 [jsParamFunc callWithArguments:@[@{@"age": @10, @"name": @"lili", @"height": @158}]];
}

獲取我們在HTML中定義的jsParamFunc方法,然後調用它並傳了一個字典作為參數。

步驟有點多,但是理順了確實很好用.

JavaScriptCore使用注意

JavaStript調用本地方法是在子線程中執行的,這裡要根據實際情況考慮線程之間的切換,而在回調JavaScript方法的時候最好是在剛開始調用此方法的線程中去執行那段JavaStript方法的代碼

根據url前綴特殊處理(協議攔截)-簡單傳遞參數

在UIWebView的代理方法:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType判斷url前綴,然後,根據協議類型進行特殊處理.

如果需要從url接受參數,我們可以把參數拼接到url上,從而傳遞到原生中.但是這種方式局限比較大

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSString *urlStr = request.URL.absoluteString;
    if ([urlStr rangeOfString:@"test://"].location != NSNotFound) {
        // url的協議頭是test的特殊處理
        NSLog(@"test");

        NSURL *url = [NSURL URLWithString:urlStr];
        NSString *scheme  = url.scheme;
        NSString *host = url.host;
        NSString *qurey = url.query;
        NSString *parameter = url.parameterString;

       // 根據參數做進一步處理
       // TODO

        return NO;
    }
    return YES;
}


  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved