javascript
浅谈 JavaScriptCore
?
來源:XcodeMen(王瑞華) ??
鏈接:http://t.cn/RVqQI5p
?
本文由我們團隊的王瑞華童鞋撰寫。
?
?
OS X Mavericks 和 iOS 7 引入了 JavaScriptCore 庫,它把 WebKit 的 JavaScript 引擎用 Objective-C 封裝,提供了簡單,快速以及安全的方式接入世界上最流行的語言。在項目的實際開發中,由于需要與 H5 端有交互,所以采用了JavaScriptCore的交互方式,借此參與該項目的機會,對JavaScriptCore也有了一些簡單的了解。
?
JSContext/JSValue
?
JSContext 是 JavaScript 的執行環境。所有 JavaScript 執行發生的背景,以及所有 JavaScript 值被綁在這一個上下文中。JSContext 類似于 UIWindow 的概念,所有的執行都在改環境中發生:
?
JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"var num = 5 + 5"];
[context evaluateScript:@"var names = ['Grace', 'Ada', 'Margaret']"];
[context evaluateScript:@"var triple = function(value) { return value * 3 }"];
JSValue *tripleNum = [context evaluateScript:@"triple(num)"];
NSLog(@"Tripled: %d", [tripleNum toInt32]);
// Tripled: 30
?
代碼最后一行,每個 JSValue 起源于 JSContext 和 持有它的強引用。當一個 JSValue 實例方法創造一個新的 JSValue時,該新 JSValue 起源于同一個 JSContext。 JSValue 包裝了每一個可能的 JavaScript 值:字符串和數字;數組、對象和方法;甚至錯誤和特殊的 JavaScript 值諸如 null 和 undefined。
?
| nil | undefined |
| NSNull | null |
| NSString | string |
| NSNumber | number, boolean |
| NSDictionary | Object object |
| NSArray | Array object |
| NSDate | Date object |
| NSBlock (1) | Function object (1) |
| id (2) | Wrapper object (2) |
| Class (3) | Constructor object (3) |
?
下標支持
?
作為下標傳遞的對象鍵將被轉換為一個 JavaScript 值, 然后值轉換為一個用作獲取屬性名稱的字符串。
?
JSValue *names = context[@"names"];
JSValue *initialName = names[0];
NSLog(@"The first name: %@", [initialName toString]);
// The first name: Grace
?
該簡易方法對應于以下兩個方法:
?
- (JSValue *)objectForKeyedSubscript:(id)key;
- (JSValue *)objectAtIndexedSubscript:(NSUInteger)index;
?
調用方法
?
JSValue 包裝了一個 JavaScript 函數,我們可以從 Objective-C 代碼中使用 Foundation 類型作為參數來直接調用該函數。
?
JSValue *tripleFunction = context[@"triple"];
JSValue *result = [tripleFunction callWithArguments:@[@5] ];
NSLog(@"Five tripled: %d", [result toInt32]);
?
JavaScript 調用
?
上面說明的 OC 調用 JavaScript 的方法。那么接下來,說明 JavaScript 訪問 OC 定義的對象和方法。我粗淺的認為就是在遵守該協議后,JavaScript 就可以調用 OC 實現的方法和屬性等。
?
讓 JSContext 訪問我們的本地客戶端代碼的方式主要有兩種:JSExport 協議和block。
?
JSExport 協議
?
JSExport 提供一個將 OC 中的類、實例方法和屬性等導出為 JavaScript 函數的方法。
?
該協議只包含了一個方法:
?
JSExportAs(PropertyName, Selector)
?
即當導出到 JavaScript 時,重命名一個 selector。
?
這就需要熟悉它的做法,當帶有一個或多個參數的 seletor 轉變為 JavaScript 的屬性名字時,在默認情況下一個屬性名字將采用以下方式生成:
?
-
所有的冒號將從 Selector 當中移除掉
-
任何一個后邊跟著冒號的小寫字母開頭的單詞將被改換為大寫。
?
在這種默認的轉換方式下,一個 selector 如 doFoo:withBar: 將被導為doFooWithBar 。這種默認的改變有可能被 JSExportAs 宏 所改寫,例如將 doFoo:withBar: 導出為 doFoo 。實際寫法如下:
?
@protocol MyClassJavaScriptMethods
????JSExportAs(doFoo,
????- (void)doFoo:(id)foo withBar:(id)bar
????);
@end
?
請注意 JSExport 宏只可能適用于帶有一個或更多的參數的selector。
?
Blocks
?
當一個 Objective-C block 被賦給 JSContext 里的一個標識符,JavaScriptCore 會自動的把 block 封裝在 JavaScript 函數里。如下:
?
JSContext *context = [[JSContext alloc] init];
????context[@"simplifyString"] = ^(NSString *input) {
????????NSMutableString *mutableString = [input mutableCopy];
????????CFStringTransform((__bridge CFMutableStringRef)mutableString, NULL, kCFStringTransformToLatin, NO);
????????return mutableString;
????};
NSLog(@"%@", [context evaluateScript:@"simplifyString('什么!')"]);
// shén me!
?
JSManagedValue 與內存管理
?
由于 block 可以保有變量引用,而且 JSContext 也強引用它所有的變量,為了避免強引用循環需要特別小心。OC 采用的是引用計數機制,而 JavaScript采用的垃圾回收機制?;诖隧棛C制,便出現了 JSManagedValue ,它的主要用途是存儲一個 JSValue,這樣可以避免一個保留環的產生。
?
JavaScript 與 OC 交互的實際實例
?
在開發項目中,我們采用了 JavaScript 的方式完成了與 Native 的交互,使得比單純的運用 UIWebview 的 stringByEvaluatingJavaScriptFromString: 更加高效。
?
如上所述,讓 JSContext 訪問我們的本地客戶端代碼的方式主要有兩種:JSExport 協議和block。在我們項目中主要采用了 JSExport 的方式去實現。
?
1. JDRWebViewJSExportProtocol 和 JDRWebViewBaseHandler
?
我們的 JDRWebViewBaseHandler 類實現了 JDRWebViewJSExportProtocol 協議,該協議規定了那些方法或者屬性可在 JavaScript 中使用。
?
@protocol JDRWebViewJSExportProtocol
?
JSExportAs
(closeForJS /**H5調用的 Webview 關閉方法的別名**/,
@optional
- (void)close:(id)JSON callback:(JSValue*) callback
);
?
JSExportAs
(goBackForJS /**H5調用的 Webview 關閉方法的別名**/,
@optional
- (void)goBack:(id)JSON callback:(JSValue*) callback
);
@end
?
@interface JDRWebViewBaseHandler : NSObject
@property (nonatomic , weak) JDRWebViewController *webViewController;
?
-(instancetype)initWithWebViewController:(JDRWebViewController *) webViewController NS_DESIGNATED_INITIALIZER;
@end
?
@implementation JDRWebViewBaseHandler
-(instancetype)initWithWebViewController:(JDRWebViewController *) webViewController{
????if (self = [super init]) {
????????_webViewController = webViewController;
????}
????return self;
}
?
-(void)goBack:(id)JSON callback:(JSValue *)callback {
????WEAK_SELF(weakSelf);
????dispatch_async(dispatch_get_main_queue(), ^{
????????STRONG_SELF(strongSelf, weakSelf);
????????//判斷 backPressCallBack 是否已經設置 ,如果已經設置監聽方法 , 則執行 監聽回退的 JS 方法
????????if ([strongSelf.webViewController.webView canGoBack]) {
????????????[(UIWebView*)strongSelf.webViewController.webView stopLoading];
????????????[(UIWebView*)strongSelf.webViewController.webView goBack];
????????}else {
????????????[strongSelf close:nil callback:nil];
????????}
????});
}
-(void)close:(id)JSON callback:(JSValue *)callback {
????//目前關閉 WebView 方法不關注 callback 參數
????WEAK_SELF(weakSelf);
????dispatch_async(dispatch_get_main_queue(), ^{
????????STRONG_SELF(strongSelf, weakSelf);
????????if ([strongSelf.webViewController.navigationController???????????? popViewControllerAnimated:YES] == nil) {
????????????????[strongSelf.webViewController???????????????????????????? dismissViewControllerAnimated:YES completion:nil];
????????????}
????});
}
@end
?
2. JSContext 配置
?
然后,我們可以用我們已經創建的 JDRWebViewBaseHandler 類,我們需要將其導出到 JavaScript 環境中。
?
self.JSHandler = [[JDRWebViewBaseHandler alloc] initWithWebViewController:self];????
// 以 JSExport 協議關聯 native 的方法 *platform* 是暫時定義的關鍵字
self.context[@"platform"] = self.JSHandler;
?
3.在 H5 頁面中書寫 JavaScript 函數調用 OC
?
close: function() {
??platform.coselForJS();
}
?
結語
?
通常對于 JavaScript 與 OC 的交互,會采用 JavaScriptCore 包裝一層協議方法,原始一點的方法則是通過截取 UIWebView 的協議實現。隨著 iOS 8 之后 WKWebview的出現,又出現了一個新的方式,而對于 WKWebView 來說是不支持直接與 JavaScriptCore 交互的。通常采取 WKUserContentController 采用它的協議方法
?
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
?
如果使用 WKWebview 的話,對于 H5 中的 JavaScript 寫法又需要改動為 window.webkit.messageHandlers..postMessage()。WKWebView 對于JavaScript 的調用只提供了一種方法:
?
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler
?
目前項目中考慮到 應用 WKWebview 與 UIWebview + JavaScriptCore 調用方式的差異,暫時放棄了對于 WKWebview的支持。
?
參考
?
JavaScriptCore
http://nshipster.cn/javascriptcore/
?
轉載于:https://www.cnblogs.com/fengmin/p/5968435.html
總結
以上是生活随笔為你收集整理的浅谈 JavaScriptCore的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于CALayer的contentsGr
- 下一篇: Visual Studio插件