对于大多开发者来讲,苹果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>
复制代码