1. 程式人生 > >JS&iOS原生互動 附程式碼地址

JS&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,可傳引數 JS傳入引數到OC
各種資料型別可以轉換,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;
方法可以反過來將引數傳進去來呼叫方法。 OC引數傳入JS

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 <JSExport>

// 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 <JavaScriptObjectiveCDelegate>

@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中新增以下程式碼:

<div style="margin-top: 100px">
<h1>Test how to use objective-c call js</h1>
<input type="button" value="Call ObjC system camera" onclick="OCModel.callSystemCamera()">
<input type="button" value="Call ObjC system alert" onclick="OCModel.showAlertMsg('js title', 'js message')">
</div>

<div>
<input type="button" value="Call ObjC func with JSON " onclick="OCModel.callWithDict({'name': 'testname', 'age': 10, 'height': 170})">
<input type="button" value="Call ObjC func with JSON and ObjC call js func to pass args." onclick="OCModel.jsCallObjcAndObjcCallJsWithDict({'name': 'testname', 'age': 10, 'height': 170})">
</div>

<div>
<span id="jsParamFuncSpan" style="color: red; font-size: 50px;"></span>
</div>

現在就可以測試程式碼了。

當我們點選第一個按鈕: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;
}

這種方式同樣需要先和服務端做一次統一

附檔案Demo地址:https://github.com/zhanglaying/JavaScript-OC-

相關推薦

JS&iOS原生互動 程式碼地址

關於原生和hybid之爭,這裡不做探討.主要講講JS和OC互動 OC執行JS程式碼 1.stringByEvaluatingJavaScriptFromString 這個方法是UIWebView裡面的方法,也是最為簡單的和JS互動的方式- (nullable

JS&iOS原生互動

關於原生和hybid之爭,這裡不做探討.主要講講JS和OC互動 OC執行JS程式碼 1.stringByEvaluatingJavaScriptFromString 這個方法是UIWebView裡面的方法,也是最為簡單的和JS互動的方式- (nullable NS

【Swift】關於Swift3.0 JS原生互動 時的一些問題

之前的註冊模型的方法 // 初始化model let model = JSAndSwiftMiddle() model.delegate = self model.webView = webView self.jsco

WebBrowser控制元件中JS原生互動

在wp8中,可以通過監聽WebBrowser控制元件的ScriptNotify方法,來獲取JS對原生的呼叫。當然JS必須呼叫特定的方法:window.external.notify. 然後可以通過呼叫Webbrowser的InvokeScript方法回撥JS,這樣即實現了雙

h5和iOS原生互動,h5和iOS互相傳值

前言 : h5和iOS原生互動,互相傳值,下面程式碼是完整的.M檔案  簡單實現h5和原生互動,互相傳值。 // //  ViewController.m //  原生-H5簡單互動 // //  Created by Mr Yang on 2018/7/23. //  C

JS原生互動原理淺析

Java實現原理研究 參照Android:WebView與Javascript互動(相互呼叫引數、傳值)http://itfish.net/article/25514.html 研究了安卓java的功能介面程式碼,上述地址先在Activity

IOS 原生介面和Weex容器互相跳轉實踐 部分js原生程式碼

weex相關原理,請看官方網站 此IOS Weex demo實現的功能,從原生介面跳轉到Weex容器頁面,然後點選Weex容器頁面的button(js)跳轉到另一個原生介面。 一. 先上效果圖   二.實現 1. weex容器渲染的js程式碼如下,上面

iOS原生程式碼通過webView與js指令碼互動

  前段時間公司的一個專案需要使用的到OC程式碼與js指令碼的互動。對於入行不久的我,當時也是在部落格裡面爬文來解決。做下來之後把我自己通過實踐和學習得來的東西整理一下,以便在這方面接觸不多的人能夠快速的掌握OC與js的互動。新手教程,大神勿噴,如有錯誤,多多指教。    

cordova + ionic前端框架 js和android ios原生(native)互動

因為專案是大部分程式碼是js+html 寫的,現在想在js中開啟原生的頁面(Android為activity;ios是ViewController),解決android 的時候發現了兩種方法,其中一種是android和ios通用的,另一種只能在android上使用。 -、a

Unity3D 嵌入iOS原生程式碼,並實現unity iOS之間的互動

Unity iOS嵌入 互動 傳值 專案需要用到iOS和Unity之間的互動, 就捯飭了一下 我專案是Unity工程大, iOS工程小, 所以在是Unity匯出的xcode工程裡嵌入iOS

[Cordova]JS和Native互動實現關鍵程式碼(iOS)

一、JS中定義的exec函式:define("cordova/exec", function(require, exports, module) 。關鍵實現程式碼如下:1.建立command物件,並且將

WebViewJavascriptBridge 使用 js調iOS原生代碼

創建 smi creat color web var key urn cti js代碼和原生ios代碼進行交互使用WebViewJavascriptBridge非常簡化了我們的操作特別是在ios這邊 js 掉用ios原生代碼時要註意的幾個事項: 1、js和ios定義好相互調

WebViewJavascriptBridge實現js與android和ios原生交互

WebViewJavascriptB Android IOS js 1、實現原生與js交互 <!-- 申明交互 這段代碼固定必須有 --> function setupWebViewJavascriptBridge(callback) { //android使用

總結篇:iOSJS原生OC互相調用

html中 web har 項目 ise ref text uia oc調用js iOS開發免不了要與UIWebView打交道,然後就要涉及到JS與原生OC交互,今天總結了一下JS與原生OC交互的兩種方式。 JS調用原生OC篇 方式一 第一種方式是用JS發起一個假的URL請

[IOS]Uiwebview+js,點選圖片得到地址

網上的教程你複製我,我複製你,特別容易誤導像我這樣的新手,好不容易找到一個方法,卻因為 網上給的js程式碼用了系統的click方法,導致一直無效。 網頁中圖片的節點是 img,利用js 得到圖片節點下的所有地址,並且為每一個圖片新增點選事件 點選時,觸發一個url,即圖片的地址。

webapp js與安卓,ios怎麼互動

/*這段程式碼是固定的,必須要放到js中*/function setupWebViewJavascriptBridge(callback) { var u = navigator.userAgent; var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X

ios webView怎麼實現JS調原生

1)在方法- (void)webViewDidFinishLoad:(UIWebView *)webView 中 self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScr

js原生應用常用的資料互動方式

場景1 在原生app中經常會使用到H5頁面,比如說電商中的活動頁,一些電商中的詳情頁,等等...這些頁面都有一個特點,那就是在未來修改的可能性,和一次性的機率特別的大。所以用H5的頁面是最睿智的一種選擇。 一旦使用了H5那麼就少不了和原生開發的一些互動(Android, IOS)如下的方案能夠幫助你解決。

RN中JS原生端相互通訊方式解析-IOS

JavaScriptCore框架 是一個蘋果在iOS7引入的框架,該框架讓 Objective-C 和 JavaScript 程式碼直接的互動變得更加的簡單方便。 而JavaScriptCore是蘋果Safari瀏覽器的JavaScript引擎,或許你聽過Google的V8

React Native和iOS原生方法互動

原生傳遞引數給React Native 初始化時傳值 - (instancetype)initWithBundleURL:(NSURL *)bundleURL moduleName:(NSString *