iOS開發(fā)筆記 - Objective-C和JavaScript混編

最近看了一個(gè)對(duì)Github上面編程語(yǔ)言使用統(tǒng)計(jì)的排行榜,JavaScript真可以說(shuō)是一枝獨(dú)秀,很難想象20年前,這個(gè)語(yǔ)言只是瀏覽器中的裝飾性語(yǔ)言,能做的事情也就是一點(diǎn)特效或者檢查一下要提交給服務(wù)器的表單是否滿足要求。今天的JavaScript已經(jīng)是一個(gè)全棧語(yǔ)言,從客戶端到服務(wù)器無(wú)所不在。很多編程語(yǔ)言都提供了跟JavaScript進(jìn)行交互的接口,這一點(diǎn)在iOS開發(fā)中也不例外。
??iOS7以前,在App中調(diào)用JavaScript的方式只有一種,就是通過(guò)UIWebView對(duì)象的stringByEvaluatingJavaScriptFromString:方法。由于UIWebView中包含了CSS渲染引擎和JavaScript執(zhí)行引擎(說(shuō)白了就是微型一個(gè)瀏覽器),因此這個(gè)方法可以讓UIWebView通過(guò)它的JavaScript運(yùn)行時(shí)環(huán)境執(zhí)行JavaScript代碼,但是能做的事情非常有限,我們可以先看看下面的例子。

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    // 獲得UIWebView中加載頁(yè)面的標(biāo)題
    NSString *title = [webView stringByEvaluatingJavaScriptFromString:
        @"document.title"];
    NSLog(@"%@", title);
    // 獲得UIWebView中加載頁(yè)面的鏈接地址
    NSString *urlStr = [webView stringByEvaluatingJavaScriptFromString:
        @"location.href"];
    NSLog(@"%@", urlStr);
}

從iOS7開始,我們可以使用JavaScriptCore框架來(lái)讓我們的Objective-C代碼和JavaScript進(jìn)行深度交互,簡(jiǎn)單的說(shuō)我們可以在Objective-C代碼中訪問(wèn)JavaScript中的變量或調(diào)用JavaScript的函數(shù),也可以JavaScript中使用Objective-C的對(duì)象和方法。我們可以先看一個(gè)簡(jiǎn)單的例子。

先加入JavaScriptCore的頭文件。

#import <JavaScriptCore/JavaScriptCore.h>

在Objective-C中使用JavaScript的正則表達(dá)式驗(yàn)證字符串。

    // 創(chuàng)建JavaScript執(zhí)行環(huán)境(上下文)
    JSContext *context = [[JSContext alloc] init];
    NSString *funCode =
        @"var isValidNumber = function(phone) {"
         "    var phonePattern = /^1[34578]\\\\d{9}$/;"
         "    return phone.match(phonePattern);"
         "};";
    // 執(zhí)行上面的JavaScript代碼
    [context evaluateScript:funCode];
    // 獲得isValidNumber函數(shù)并傳參調(diào)用
    JSValue *jsFunction = context[@"isValidNumber"];
    JSValue *value1 = [jsFunction callWithArguments:@[ @"13012345678" ]];
    NSLog(@"%@", [value1 toBool]? @"有效": @"無(wú)效");    // 有效
    JSValue *value2 = [jsFunction callWithArguments:@[ @"12345678899" ]];
    NSLog(@"%@", [value2 toBool]? @"有效": @"無(wú)效");    // 無(wú)效

在Objective-C中調(diào)用JavaScript函數(shù)求階乘。

    // 創(chuàng)建JavaScript執(zhí)行環(huán)境(上下文)
    JSContext *context = [[JSContext alloc] init];
    // 可以將一個(gè)block傳給JavaScript上下文
    // 它會(huì)被轉(zhuǎn)換成一個(gè)JavaScript中的函數(shù)
    context[@"factorial"] = ^(int x) {
        double result = 1.0;
        for (; x > 1; x--) {
            result *= x;
        }
        return result;
    };
    // 執(zhí)行求階乘的函數(shù)
    [context evaluateScript:@"var num = factorial(5);"];
    JSValue *num = context[@"num"];
    NSLog(@"5! = %@", num);    // 5! = 120

JavaScript和Objective-C中類型的對(duì)應(yīng)關(guān)系如下表所示:

Objective-C類型 JavaScript類型
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

再來(lái)看一個(gè)例子。我們?cè)诟晥D控制器中放置一個(gè)按鈕,點(diǎn)擊后會(huì)導(dǎo)航到下一個(gè)視圖控制器,其中有一個(gè)UIWebView加載了一個(gè)網(wǎng)頁(yè),頁(yè)面中有一顆按鈕,我們希望點(diǎn)擊按鈕后導(dǎo)航到上一個(gè)視圖控制器,要做到這一點(diǎn),就需要在JavaScript中訪問(wèn)Objective-C對(duì)象和方法。

圖1. Storyboard中視圖控制器的導(dǎo)航關(guān)系

頁(yè)面的代碼:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>測(cè)試頁(yè)面</title>
        <style type="text/css">
            #backButton { 
                display: inline-block;
                width:50px; height:30px;
            }
        </style>
    </head>
    
    <body>
        <button id="backButton">返回</button>
        <script type="text/javascript">
            var btn = document.getElementById("backButton");
            var cb = function() {
                window.alert('Hello');
            };
            btn.addEventListener('click', cb, false);
        </script>
    </body>
</html>

第二個(gè)視圖控制器的代碼:

#import "ViewController.h"
#import <JavaScriptCore/JavaScriptCore.h>

@protocol MyProtocol <JSExport>

- (void) letsGoBack;

@end

@interface SecondViewController : ViewController <MyProtocol>

@end
@interface SecondViewController () <UIWebViewDelegate>

@property (weak, nonatomic) IBOutlet UIWebView *myWebViw;

@end

@implementation SecondViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [_myWebViw loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:
        @"http://localhost:8080/myweb/test.html"]]];
    _myWebViw.delegate = self;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    // 通過(guò)UIWebView獲得網(wǎng)頁(yè)中的JavaScript執(zhí)行環(huán)境
    JSContext *context = [webView valueForKeyPath:
        @"documentView.webView.mainFrame.javaScriptContext"];
    // 設(shè)置處理異常的block回調(diào)
    [context setExceptionHandler:^(JSContext *ctx, JSValue *value) {
        NSLog(@"error: %@", value);
    }];
    
    context[@"callBackObj"] = self;
    // 下面的代碼移除了按鈕原先綁定的事件回調(diào)重新綁定返回上一個(gè)視圖控制器的代碼
    NSString *code =
                   @"var btn = document.getElementById('backButton');"
                    "btn.removeEventListener('click', cb);"
                    "btn.addEventListener('click', function() {"
                    "   callBackObj.letsGoBack();"
                    "});";
    [context evaluateScript:code];
}

// 實(shí)現(xiàn)協(xié)議中的方法
- (void) letsGoBack {
    // 必須回到主線程刷新用戶界面
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.navigationController popViewControllerAnimated:YES];
    });
}

@end

可以在Github上下載到上面例子的完整代碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,029評(píng)論 4 61
  • 這兩天我算是重新認(rèn)識(shí)了一個(gè)詞——虛電,尤其是今天我被它坑了之后,我想我這輩子都不會(huì)忘記“虛電”這個(gè)詞了。 ...
    辰星劇社馬沖閱讀 988評(píng)論 0 0

友情鏈接更多精彩內(nèi)容