對於大多開發者來講,蘋果WWDC2018大會關注比較多的是iOS 十二、新的ARKit、新的CoreML,其實還有一個更改在session上沒有具體說起,但對於開發者來講影響挺大,以下圖: javascript
有些項目適配起來相對比較簡單,由於不涉及複雜的交互邏輯,只須要替換底層的Web容器,改下調用的API就好了,可是在適配一個較爲複雜的跨端交互項目時就比較gg了,由於WKWebView不使用JavaScriptCore相關的API,而是使用本身的一套機制進行JS與原生橋接,並且JS調用方式與JavaScriptCore徹底不一樣,若是原生只是簡單地替換下容器和改下交互的API,那咱們的h5同窗又得加班加點了,上層JS API在新的容器是沒法調用。java
爲了h5同窗完美過渡,無需改動任何橋接代碼,只需關注本身的業務迭代,咱們的橋接框架須要來一次大升級,代碼「WK」。由於不能使用JavaScriptCore,因此XDMicroJSBridge底層橋接框架得從新設計,此次咱們使用WKWebView的WKScriptMessageHandler進行橋接層的設計。git
咱們先看下新的框架接口層的設計: github
惟一的改變就是容器這一塊的API交互,之前是使用方傳進容器,如今是咱們裏面提供配置好的容器給外部,下降原生同窗使用的複雜度,確實WKWebView這個容器相比UIWebView這個容器更加靈活,各類各樣的配置項。web
- (WKWebView *)getBridgeWebView {
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
// 設置偏好設置
config.preferences = [[WKPreferences alloc] init];
// 默認爲0
config.preferences.minimumFontSize = 10;
// 默認認爲YES
config.preferences.javaScriptEnabled = YES;
// 在iOS上默認爲NO,表示不能自動經過窗口打開
config.preferences.javaScriptCanOpenWindowsAutomatically = NO;
config.processPool = [[WKProcessPool alloc] init];
config.userContentController = [[WKUserContentController alloc] init];
//解決self 循環引用問題
XDWKWeakScriptMessageDelegate *weakSelf = [[XDWKWeakScriptMessageDelegate alloc] initWithDelegate:self];
[config.userContentController addScriptMessageHandler:weakSelf name:@"XDWKJB"];
WKUserScript *injectScript = [[WKUserScript alloc] initWithSource:injectJS injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[config.userContentController addUserScript:injectScript];
_webview = [[WKWebView alloc] initWithFrame:CGRectNull configuration:config];
return _webview;
}
複製代碼
這裏有個注意點就是WKWebView在註冊JS的消息回調這一塊須要弄個額外的對象進行回調註冊,若是直接用self會形成循環引用致使內存泄露,通過實驗寫成weakSelf也無論用。
WKWebView有我的性化的設計就是可以原生化的進行JS預加載,並且可以指定加載時機和位置,相比之下,之前在UIWebView進行JS預加載一點都不pure。objective-c
WKWebView提供WKScriptMessageHandler進行JS層的消息捕獲,例如註冊一個名爲XDWKJB的橋接對象,JS層那邊經過window.webkit.messageHandlers.XDWKJB.postMessage(e)
這個方法將消息e發送到原生這邊,原生這邊在WKScriptMessageHandler的回調方法- (void)userContentController:(nonnull WKUserContentController *)userContentController didReceiveScriptMessage:(nonnull WKScriptMessage *)message
進行消息的捕獲和解析。
此次的設計須要引入必定JS代碼,由於WKWebView提供的JS交互只支持簡單的數據傳輸,當存在callback的時候,當前的交互並不能將js的callback完美轉化。因此須要設計一個js的callback管理器用於保存從js的callback,在js層先賦予callback一個id並映射起來,由於id能夠設計爲簡單的數據格式,如字符串,因此原生這邊很容易捕獲和解析,當原生回調數據時經過callback管理器獲取對應id的callback,執行對應callback的js代碼就能夠實現橋接回調,代碼實例以下:api
var XDMCBridge = {};var xd_jscallback_center={_callbackbuf:{},addCallback:function(a,c){\"function\"==typeof c&&(this._callbackbuf[a]=c)},fireCallback:function(a,c){if(\"string\"==typeof a){var f=this._callbackbuf[a];\"function\"==typeof f&&(void 0===c?f():f(c))}}};
複製代碼
由於上個版本的XDMicroJSBridge提供的JS API是類微信web API風格,咱們此次繼續沿用這種代碼風格,爲了適配這種風格,因此此次的框架須要提供一些模板JS代碼來輔助JS方法註冊和注入,模板實例以下:微信
%@.%@=function(){var a=arguments.length,e={methodName:\"%@\"},l=Array.from(arguments);a>0&&(\"function\"==typeof l[a-1]?(e.callbackId=\"%@\",e.params=a-1>0?l.slice(0,a-1):[]):e.params=l),null!=e.callbackId&&xd_jscallback_center.addCallback(e.callbackId,l[a-1]),window.webkit.messageHandlers.XDWKJB.postMessage(e)};
複製代碼
%@是佔位符,用於替換註冊的JS接口的命名空間名和方法名session
XDMicroJSBridge拓展出XDMicroJSBridge_WK,用於適配WKWebView,一樣支持命名空間,原生專一原生代碼,web專一JavaScriptapp
#import "XDMicroJSBridge_WK.h"
@property (nonatomic, strong) WKWebView *webView;
@property (nonatomic, strong) XDMicroJSBridge_WK *bridge;
@property (nonatomic, copy) XDMCJSBCallback callback;
self.bridge = [[XDMicroJSBridge_WK alloc] init];
複製代碼
跟上個版本註冊方式如出一轍
__weak typeof(self) weakself = self;
[_bridge registerAction:@"camerapicker" handler:^(NSArray *params, XDMCJSBCallback callback) {
dispatch_async(dispatch_get_main_queue(), ^{
//if your javaScript method has callback, you should register this call like this.
if (callback) {
weakself.callback = callback;
}
UIImagePickerController *cameraVC = [[UIImagePickerController alloc] init];
cameraVC.delegate = weakself;
cameraVC.sourceType = UIImagePickerControllerSourceTypeCamera;
[weakself presentViewController:cameraVC animated:YES completion:nil];
});
}];
複製代碼
也跟上個版本調用如出一轍,h5同窗會不會很開心😊,不用改代碼
<script>
function clickcamera() {
XDMCBridge.camerapicker(function (response) {
var photos = response['photos'];
var insert = document.getElementById('insert');
for(var i = 0; i < photos.length; i++) {
var img = new Image(100,100);
img.src = photos[i];
insert.appendChild(img);
}
});
}
</script>
複製代碼