Objective-C與JavaScript交互的那些事

nvwb6154 8年前發布 | 24K 次閱讀 Objective-C開發 JavaScript Objective-C

來自: http://www.cocoachina.com/ios/20160127/15105.html

最近公司的運營瞎搞了個活動,其活動要服務端提供數據支持,web前端在微信公眾賬號內作為主要的運營陣地,而iOS、Android要提供相應的入口及頁面進行配合。一個活動,動用了各個端的程序猿。而在這里面技術方面主要就是涉及到web端和服務端的交互,web前端和iOS、Android的交互。本人作為一個iOS開發者,今天就聊聊web、iOS、Android三端的交互,其實在說明白一點就是方法的互相調用而已。這里主要講解iOS。Android會稍微提一下,僅作參考。

此篇文章的邏輯圖

圖0-0 此篇文章的邏輯圖

概述

iOS原生應用和web頁面的交互大致上有這幾種方法iOS7之后的JavaScriptCore、攔截協議、第三方框架WebViewJavaScriptBridge、iOS8之后的WKWebView在這里主要講解JavaScriptCore和攔截協議這兩種辦法。WebViewJavaScriptBridge是基于攔截協議進行的封裝。學習成本相對JavaScriptCore較高,使用也不如JavaScriptCore方便本文不做敘述。WKWebView是iOS8之后推出的,還沒有成為主流使用,所以本篇文章也不做詳細敘述。

Objective-C執行JavaScript代碼

相關方法

// UIWebView的方法

  • (nullable NSString )stringByEvaluatingJavaScriptFromString:(NSString )script;

// JavaScriptCore中JSContext的方法

  • (JSValue )evaluateScript:(NSString )script;
  • (JSValue )evaluateScript:(NSString )script withSourceURL:(NSURL *)sourceURL</pre>

    相關應用

    用這些方法去執行大段的JavaScript代碼是沒什么必要的,但是有些小場景用起來還是比較順手和實用的,列舉兩個例子作為參考:

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

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

JavaScriptCore

iOS7之后蘋果推出了JavaScriptCore這個框架,從而讓web頁面和本地原生應用交互起來非常方便,而且使用此框架可以做到Android那邊和iOS相對統一,web前端寫一套代碼就可以適配客戶端的兩個平臺,從而減少了web前端的工作量。

web前端

在三端交互中,web前端要強勢一些,一切傳值、方法命名都按web前端開發人員來定義,讓另外兩端去做適配。在這里以調用攝像頭和分享為例來詳細講解,測試網頁代碼取名為test.html,其代碼內容如下:

test.html代碼內容 (因識別問題,用方括號替換了代碼中的尖括號)

[!DOCTYPE html]
[html]
[head]
    [meta charset="UTF-8"]
[/head]
[body]
    [div style="margin-top: 100px"]
        [h1>Objective-C和JavaScript交互的那些事[/h1]
        [input type="button" value="CallCamera" onclick="Toyun.callCamera()"]
    [/div]
[div] [input type="button" value="Share" onclick="callShare()"] [/div]

[script] var callShare = function() { var shareInfo = JSON.stringify({"title": "標題", "desc": "內容", "shareUrl": "); Toyun.share(shareInfo); }

var picCallback = function(photos) {
    alert(photos);
}

var shareCallback = function(){
    alert('success');
}

[/script] [/body] [/html]</pre>

test.html代碼解釋

可能有些同學對web前端的一些知識不太熟悉,稍微對這段代碼做下解釋,先說Toyun是iOS和Android這兩邊在本地要注入的一個對象【參考下面iOS的代碼更容易明白】,充當原生應用和web頁面之間的一個橋梁。頁面上定義了兩個按鈕名字分別為CallCamera和Share。點擊CallCamera會通過Toyun這個橋梁調用本地應用的方法- (void)callCamera,沒有傳參;而點擊Share會先調用本文件中的JavaScript方法callShare這里將要分享的內容格式轉成JSON字符串格式(這樣做是為了適配Android,iOS可以直接接受JSON對象)然后再通過Toyun這個橋梁去調用原生應用的- (void)share:(NSString *)shareInfo方法這個是有傳參的,參數為shareInfo。而下面的兩個方法為原生方法調用后的回調方法,其中picCallback為獲取圖片成功的回調方法,并且傳回拿到的圖片photos;shareCallback為分享成功的回調方法。

iOS

iOS這邊根據前端定義的方法名來寫代碼,但是有些時候web前端會讓我們定義,但是我們定義好之后他又要修改,這時候就會很煩啊。所以碰到三端交互的時候最好就是讓web前端去定義方法名,iOS和Android根據web前端定義好的去寫代碼。JavaScriptCore中web頁面調用原生應用的方法可以用Delegate或Block兩種方法,此文以按Delegate講解。

JavaScriptCore中類及協議:

  • JSContext:給JavaScript提供運行的上下文環境

  • JSValue:JavaScript和Objective-C數據和方法的橋梁

  • JSManagedValue:管理數據和方法的類

  • JSVirtualMachine:處理線程相關,使用較少

  • JSExport:這是一個協議,如果采用協議的方法交互,自己定義的協議必須遵守此協議

ViewController中的代碼

#import "ViewController.h"

import JavaScriptCore/JavaScriptCore.h

@protocol JSObjcDelegate JSExport

  • (void)callCamera;
  • (void)share:(NSString *)shareString;

@end

@interface ViewController () UIWebViewDelegate, JSObjcDelegate

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

@end

@implementation ViewController

pragma mark - Life Circle

  • (void)viewDidLoad { [super viewDidLoad];

    NSURL *url = [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"html"]; [self.webView loadRequest:[[NSURLRequest alloc] initWithURL:url]]; }

pragma mark - UIWebViewDelegate

  • (void)webViewDidFinishLoad:(UIWebView )webView { self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; self.jsContext[@"Toyun"] = self; self.jsContext.exceptionHandler = ^(JSContext context, JSValue *exceptionValue) {
      context.exception = exceptionValue;
      NSLog(@"異常信息:%@", exceptionValue);
    
    }; }

pragma mark - JSObjcDelegate

  • (void)callCamera { NSLog(@"callCamera"); // 獲取到照片之后在回調js的方法picCallback把圖片傳出去 JSValue *picCallback = self.jsContext[@"picCallback"]; [picCallback callWithArguments:@[@"photos"]]; }

  • (void)share:(NSString )shareString { NSLog(@"share:%@", shareString); // 分享成功回調js的方法shareCallback JSValue shareCallback = self.jsContext[@"shareCallback"]; [shareCallback callWithArguments:nil]; }

@end</pre>

ViewController中的代碼解釋

自定義JSObjcDelegate協議,而且此協議必須遵守JSExport這個協議,自定義協議中的方法就是暴露給web頁面的方法。在webView加載完畢的時候獲取JavaScript運行的上下文環境,然后再注入橋梁對象名為Toyun,承載的對象為self即為此控制器,控制器遵守此自定義協議實現協議中對應的方法。在JavaStript調用完本地應用的方法做完相對應的事情之后,又回調了JavaStript中對應的方法,從而實現了web頁面和本地應用之間的通訊。

JavaScriptCore使用注意

JavaStript調用本地方法是在子線程中執行的,這里要根據實際情況考慮線程之間的切換,而在回調JavaScript方法的時候最好是在剛開始調用此方法的線程中去執行那段JavaStript方法的代碼,我在實際運用中開始沒注意,就被坑慘了啊。什么,說的太繞,看下面的代碼解釋:

//  假設此方法是在子線程中執行的,線程名sub-thread

  • (void)callCamera {
    // 這句假設要在主線程中執行,線程名main-thread NSLog(@"callCamera");

    // 下面這兩句代碼最好還是要在子線程sub-thread中執行啊 JSValue *picCallback = self.jsContext[@"picCallback"]; [picCallback callWithArguments:@[@"photos"]]; }</pre>

    運行效果

    運行效果如圖3-1所示

    圖3-1 運行效果

    攔截協議

    攔截協議這個適合一些比較簡單的一些情況,不需要引入什么框架,只需要web前端配合一下就好。但是在具體調用哪一個方法上,以及在傳值的時候可能會有些不方便,而且調用完后無法在回調JavaScript的方法。

    web前端

    test.html中的代碼 ( 因識別問題, 用方括號替換了代碼中的尖括號 )

    [!DOCTYPE html]
    [html]
    [head]
      [meta charset="UTF-8"]
    [/head]
    [body]
      [div]

      [input type="button" value="CallCamera" onclick="callCamera()"]
    

    [/div]

[script] function callCamera() { window.location.href = 'toyun://callCamera'; } [/script] [/body] [/html]</pre>

test.html中的代碼解釋

這段代碼相比上面的那段測試代碼是很簡單的,同樣有一個按鈕,名字為CallCamera點擊之后調用自己的callCamera方法,window.location.href這里是改變主窗口的指向從而馬上發出一個鏈接為Toyun://callCamera請求,而想要傳給原生應用的參數也可已包含到此請求中,而在iOS方法中我們要攔截這個請求,根據請求內容去判斷JavaStript想要做的事情,從而實現web頁面和本地應用之間的交互。

iOS

iOS對應的代碼

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSString *url = request.URL.absoluteString;
    if ([url rangeOfString:@"toyun://"].location != NSNotFound) { 
        // url的協議頭是Toyun
        NSLog(@"callCamera");
        return NO;
    }
    return YES;
}

iOS對應的代碼的解釋

在webView的代理方法中去攔截自定義的協議Toyun://如果是此協議則據此判斷JavaStript想要做的事情,調用原生應用的方法,這些都是提前約定好的,同時阻止此鏈接的跳轉。

總結

隨著手機硬件的配置越來越強大和HTML5的興起,一個App完全可以由web頁面來寫。現在已經有部分應用這么干了,我是遇見過的,如 古詩文網 。盡管比較少但是web頁面和本地應用的交互不論是iOS還是Android都是會有遇到的。iOS我還是比較推薦JavaScriptCore,這樣三端可以相對統一起來,寫的時候都比較簡單。隨著時間的推移iOS8推出的WKWebView會逐漸成為主流,這個的功能更強大。攔截協議也只能說用到比較簡單的一些情況吧,復雜的情況處理相互之間參數的傳遞還是比較麻煩的,而且這個不能回調JavaScript的方法,確實喜歡攔截協議的同學可以研究 WebViewJavaScriptBridge 這個第三方庫。對于Android本人也就是略知皮毛而已,就不班門弄斧了,對于一些Android開發者來說,可以看地第一段的test.html這個頁面的寫法完全是可以適配Android的。

參考

iOS與JS交互實戰篇(ObjC版)

</div>

 本文由用戶 nvwb6154 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!