你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS插件化研究之一——JavaScriptCore

iOS插件化研究之一——JavaScriptCore

編輯:IOS開發綜合

原文:http://chentoo.com/?p=191

一、前言

一樣的開篇問題,為什麼要研究這個?iOS為什麼要插件化?為什麼要借助其他語言比如html5 js甚至腳本lua等來實現原本OC/Swift應該實現的東西?
原因可以歸結為兩點:
1. iOS平台 appstore 審核速度不可控,而很多活動頁面需要頻繁更新,如果每次更新都走appstore審核流程,那活動也就不要做了。
2. 可多平台復用代碼,節省開發成本。比如同一個活動的頁面,用html5+js完成,就可以通用的在iOS Android平台上,而只需要維護一份html5+js代碼。

現如今國內各大互聯網公司的iOS端產品,絕大多是都有使用這種技術,特別是html5+js。而使用腳本語言來做動態更新的app也不在少數。
本文先討論使用html5 + js來插件化的技術。

請浏覽一篇文章來腦補一下,我們要做啥 分析支付寶客戶端的插件機制
當然,現在的支付寶版本已經告別了這種顯性插件化的機制。後面會具體說。

另外還要補充一個前提,我們絕對不做純html5+js的app,因為稍復雜的app,使用純html5的方式,只會給自己挖坑,現階段,native+部分簡單邏輯的html5才是真正切合實際的方案。這個問題不展開討論了。


二、應該准備點什麼?

首先我們得准備點東西,當然你要熟悉OC語言(swift亦可),然後你要了解html語言,能寫幾句js。

然後我們絕不用歷史上”著名”的PhoneGap來做,因為它真的很弱。也不能簡單的使用UIWebViewDelegate的一個方法來做簡單的js 和 OC的通信,因為那是遠遠不夠的。
我們要使用的是很早就出現並廣泛運用在mac平台,但直到iOS7才進入移動平台的JavaScriptCore。這真的是iOS7開始原生提供的,真的不是私有的,真的你隨便用


三、JavaScriptCore基礎知識

3.1 JavaScriptCore是什麼?

JavaScriptCore框架是基於webkit中以C/C++實現的JavaScriptCore的一個包裝,之前廣泛應用於mac平台,從iOS7開始,apple主動將其加入到iOS SDK中。JavaScriptCore讓Objective-C和JavaScript代碼的交互變得更加簡單和直接。

JavaScriptCore中有幾個重要的東西:

#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"

他們都是日常使用中經常用到的東西。後面會結合實例,介紹他們都是干嘛的。

3.2 iOS如何使用JavaScriptCore?

在需要的地方,引入:

#import JavaScriptCore/JavaScriptCore.h

3.3 JavaScriptCore能用來做什麼?

3.3.1 通過OC執行js方法或調取js屬性。

比如下面的一個例子:

    JSContext *context = [[JSContext alloc] init];
    [context evaluateScript:@"var arr = [1, 2, 'This is js string'];var sum = function(a, b) { return a+b;}"];
    JSValue *jsArray = context[@"arr"];
    JSValue *jsSum = context[@"sum"];

    JSValue *jsSumResult = [jsSum callWithArguments:[NSArray arrayWithObjects:@12, @33, nil]];

JSContext對象是JS的運行環境,通過 -evaluateScript 方法可以運行一段javaScript。javaScript的所有變量方法都會在JSContext對象中妥善的保存。通過對JSContext對象的一些操作,可以調用javaScript的方法,或者存取javaScript的對象。

之前見到的JSVirtualMachine顧名思義,是javaScript的虛擬機,是為JSContext提供運行資源。JSVirtualMachine的具體使用在後面也會講到。
但是我們的JSContext *context 是通過 init方法生成的啊,看似並沒有攙和到JSVirtualMachine啊?但是其實,通過init方法生成的JSContext對象,在init方法內部,仍然會自動線生成一個JSVirtualMachine,然後調用JSContext 對象的 -initWithVirtualMachine 方法。故,一個JSContext對象,必定要對應著一個JSVirtualMachine對象。

同一個JSVirtualMachine中的若干個JSContext可以互相交換方法對象等等,但是不同的JSVirtualMachine不能互相交換任何JSContext的資源。

下圖來自蘋果官方,很形象的描述了這一點。
2015-03-11 :(不知為何,蘋果刪除了官方文檔中關於JavaScriptCore的部分。。擦咧。。。)

JSValueJavaScriptCore中一個重要的類,前面說到,我們使用JSContextJSVirtualMachine 開拓了一個運行和保留javaScript的空間,而JSContextjavaScript的各個方法和屬性對應著JSValue

JSValue也是OCjavaScript 互相訪問和修改的中間體,所有OCjavaScript的跨語言操作都要通過JSValue一些方法進行。

比如我們示例代碼裡的,JSValue *jsArray,jsArray對應著javaScript中的一個 array對象:arr。所以我們可以對jsArray進行一些操作,從而操作javaScript 中的 arr。
例如:

jsArray[0];//1
jsArray[2];//This is js string
jsArray[1] = 49;//修改arr 的第二個元素。
jsArray[@"length"];//結果是3,調用js arr對象的方法。

又比如我們示例代碼裡的,jsSum,對應著sum function,因此我們可以通過操作jsSum從而調取javaScript中的 sum function。

    JSValue *jsSumResult = [jsSum callWithArguments:[NSArray arrayWithObjects:@12, @33, nil]];
//jsSumResult = 45;

可見,我們可以方便的通過JSValue對象的 callWithArguments:方法來直接調取 js 的 function。js function的多參數,在OC中,由NSArray組裝而成。

3.3.2 通過js執行OC方法或調取OC屬性。

蘋果介紹,有兩種方式可以方便的通過js 調用 OC:

Block 用來調用方法。 JSExport protocol 用來調用對象。

示例:

//我們有一個OC方法,提供給js調用
- (NSInteger)sumWithA:(NSInteger)a B:(NSInteger)b C:(NSInteger)c
{
    return a + b + c;
}

- (void)jsToOcFunction
{
    JSContext *context = [[JSContext alloc] init];
    context[@"sumNums"] = ^(NSInteger a, NSInteger b, NSInteger c) {
        return [self sumWithA:a B:b C:c];
    };

    JSValue *sum = [context evaluateScript:@"sumNums(7, 56, 22)"];
    NSLog(@"sum  %@", sum);//sum  85
}

在例子中,我們定義了一個OC方法:sumWithA:(NSInteger)a B:(NSInteger)b C:(NSInteger)c,提供給js調取使用。

在調取時,我們首先聲明一個JSContext,然後對context 的sumNums 賦予了一個OC的Block,Block內執行了我們之前提供的OC方法。

然後我們通過context的 -evaluateScript方法執行了一個js方法,嘗試調用OC函數。最終看到了調取成功的結果。

我們再舉一個例子,來說明JSContext 在js 調用 OC方法中的重要性。

//嘗試用OC來實現一個JavaScriptCore所不具有的Log
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-------

這個經典的例子來自於 Ider的blog

例子中有兩個關鍵點:

[JSContext currentArguments] 類方法可以拿到js函數中的所有參數列表。每個參數在OC中也都用JSValue 描述。 [JSContext currentThis] 類方法可以拿到調用該方法的對象。

需要特別注意的是:

1. 不論在任何情況下,不要在Block中直接使用外面的JSValue對象, 而應該把JSValue當做參數來傳進Block中。
2. 不論在任何情況下,不要在Block中直接使用外面的JSContext對象, 而應該使用 [JSContext currentContext]獲取。

上面我們看到了js調取OC方法的例子,下面我們看js通過JSExport protocol調取OC屬性的例子:

//使用rumtime 為一個系統控件UIButton增加JSExport protocol
@protocol UIButtonExport 
- (void)setTitle:(NSString *)title forState:(UIControlState)state;
@end

- (void)changeTitle
{
    class_addProtocol([UIButton class], @protocol(UIButtonExport));

    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    [button setTitle:@"你好 OC" forState:UIControlStateNormal];
    button.frame = CGRectMake(100, 100, 100, 100);
    [self.view addSubview:button];

    JSContext *context = [[JSContext alloc] init];
    context[@"button"] = button;
    [context evaluateScript:@"button.setTitleForState('你好 js', 0)"];
}

而除了上述通過runtime的方式增加JSExport protocol之外,還可以通過category的方式,比如:

//UIButton+js.h
#import 
#import 

@protocol UIButtonExport 
- (void)setTitle:(NSString *)title forState:(UIControlState)state;
@end

@interface UIButton (js) 
@end

可以看到,如果想要在js中調用OC 的類或者對象的方法,需要將方法在JSExport protocol中聲明。

下面再舉一個更復雜的例子。

//book.js
//列出書的title
var bookTitleList = function(book1, book2) {
    var book1Title = book1.title;
    var book2Title = book2.title;
    return 'list:'+ book1Title + book2Title;
};
//創建兩本書的合訂本
var makeBookFromTwoBooks = function(book1, book2) {
    var title = book1.title + book2.title;
    var page = book1.page + book2.page;
    return Book.makeBookWithTitlePage(title, page);
};
//
//  Book.h
#import 
#import 
@class Book;

@protocol BookExports 
@property NSString *title;
@property NSInteger page;
+ (Book *)makeBookWithTitle:(NSString *)title page:(NSInteger)page;
@end

@interface Book : NSObject 
@property NSString *title;
@property NSInteger page;
+ (Book *)makeBookWithTitle:(NSString *)title page:(NSInteger)page;
@end

//
//  Book.m
#import "Book.h"
@implementation Book
+ (Book *)makeBookWithTitle:(NSString *)title page:(NSInteger)page
{
    Book *book = [[Book alloc] init];
    book.title = title;
    book.page = page;
    return book;
}
@end
- (void)moreExportsTest
{
    JSContext *context = [[JSContext alloc] init];

    NSString* path = [[NSBundle mainBundle] pathForResource:@"book" ofType:@"js"];
    NSString *jsString = [NSString stringWithContentsOfFile:path encoding:NSStringEncodingConversionAllowLossy error:nil];

    [context evaluateScript:jsString];

    Book *book1 = [Book makeBookWithTitle:@"資治通鑒第一部" page:330];
    Book *book2 = [Book makeBookWithTitle:@"資治通鑒第二部" page:520];

    JSValue *fucntion = context[@"bookTitleList"];
    JSValue *result = [fucntion callWithArguments:@[book1, book2]];
    NSLog(@"result  %@",result.toString);
//result  list:資治通鑒第一部資治通鑒第二部

    context[@"Book"] = [Book class];

    JSValue *function = context[@"makeBookFromTwoBooks"];
    JSValue *jsResult = [function callWithArguments:@[book1, book2]];

    Book *newBook = [jsResult toObject];

    NSLog(@"newBook  title %@ page: %ld",newBook.title, (long)newBook.page);
//newBook  title 資治通鑒第一部資治通鑒第二部 page: 850
}

第一段js,是我們的javascript文件。我們在其中定義了兩個方法。

第二段,是我們定義了一個Book類,可以看到Book對象有兩個屬性:title 和 page,有一個類方法:+ (Book )makeBookWithTitle:(NSString )title page:(NSInteger)page,用來生成新的Book。這個方法將在js中被調用。
然後我們聲明了,@protocol BookExports ,將需要在js中直接訪問的屬性和方法放置進去。
最後,讓我們的Book類遵循BookExports protocol(@interface Book : NSObject )。

這樣一個可在js中直接訪問的類就生成完畢了。

最後一段是我們的運行代碼。
首先將一段js代碼加載斤JSContext中並執行。之後聲明兩個Book對象,book1 book2。將其作為參數傳入js函數bookTitleList中。可以看到執行結果與預期相同。在js中,我們直接book1.title取出了OC對象的屬性。
緊接著,我們執行了第二個js函數,在其中,用js調用了OC的Book的類方法,創建出了一個新的OC 的Book對象並返回。

到這裡,例子就講完了。可以看到通過神奇的JavaScriptCore,OC語言和javascript語言幾乎暢通無阻。而這將是我們以後構建可插件化的iOS架構的基礎。

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