小知识,大挑战!本文正在参与“程序员必备小知识”创作活动
本文同时参与「掘力星计划」,赢取创作大礼包,挑战创作激励金
通过问题看本质!!!
项目背景
我们在项目中都在使用WKWebView了,而WebKit引入了javascript引擎,只需要在WKWebView初始化的时候,添加脚本信息处理器WKScriptMessageHandler,让一个对象具有脚本信息处理能力,只要我们遵循协议方法,就能进行通信了。
项目中(HTML)统一以下方法进行交互:
Android :TCXJSbridge.postMessage(string json);
iOS-WKWebView: window.webkit.messageHandlers.TCXJSbridge.postMessage(string json);
iOS客户端中,我们只要在以下的回调方法处理业务逻辑。
- (void)userContentController:(WKUserContentController *)userContentController
didReceiveScriptMessage:(WKScriptMessage *)message{
if ([message.name isEqualToString:@"TCXJSbridge"]) {
//解析数据,执行相应的代码
}
}
定义协议
问:实现JS和客户端通信前,需要做什么准备呢?
答:JS和原生交互时,要提前定好通信协议。就像我们调试接口一样,后端提供的数据格式是固定的,这样更加利于代码的封装和后期的维护。
JS通信格式
{
"data": {
//具体的数据
appid:"20201123"
},
"callback": "callbackFunc",//回调js 函数,由js 定义
"name": "device.getAppVersion" //原生api名字
}
iOS客户端响应格式
{
status:"404",
errorMsg:"没有该JS API",
data: {
//data
appid:"20201123"
}
}
代码实现
准备工作做完后,接下来就是实际操作了。
JS调用原生代码,与iOS客户端交互实现,比如获取经纬度功能:
//获取经纬度
function getLocation(){
var json ={
data:{
appid:"20201123",
},
name:"map.getLocation",
callback:"callbackFunc"
};
window.webkit.messageHandlers.TCXJSbridge.postMessage(JSON.stringify(json));
}
// 开发工作中,测试回调代码
function callbackFunc(result){
alert(result);
console.log(JSON.parse(result));
}
iOS客户端接收到数据,处理业务逻辑,并回传数据到HTML,实现双方通信。
//初始化时,添加handler,要注意循环引用问题
TCXWebViewScriptMessageDelegate *weakScriptMessageDelegate = [[TCXWebViewScriptMessageDelegate alloc] initWithDelegate:self];
[self.webView.configuration.userContentController addScriptMessageHandler:weakScriptMessageDelegate name:@"TCXJSbridge"];
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
if ([message.name isEqualToString:@"TCXJSbridge"]) {
//解析数据,执行相应的代码
NSDictionary *body = message.body;
if (!body) {
return;
}
if ([body isKindOfClass:[NSDictionary class]]) {
NSString *actionName = body[@"name"];
if (@"map.getLocation" isEqualToString:actionName]){
//获取经纬度
NSString *callback = body[@"callback"];
if(callback){
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithCapacity:2];
[dictionary setValue:@"39.916527" forKey:@"latitude"];
[dictionary setValue:@"116.397128" forKey:@"longitude"];
NSString *jsCallback = [self getJsCallWithCallbackFunc:actionName callDic:dictionary];
//获取到js的方法,回传数据
[self.webView evaluateJavaScript:jsCallback completionHandler:^(id _Nullable object, NSError * _Nullable error) {
NSLog(@"object = %@,error = %@",object,error);
}];
}
}else if (@"user.getInfomation" isEqualToString:actionName]){
//获取用户信息
}else{
//其他操作
}
}
}
}
//组装JS回调的数据,js方法的参数为json字符串
-(NSString*)getJsCallWithCallbackFunc:(NSString*)callbackFunc callDic:(NSDictionary*)callDic{
NSMutableDictionary *dic = [NSMutableDictionary new];
[dic setValue:@(200) forKey:@"status"];
[dic setValue:callDic ? callDic : @{} forKey:@"data"];
[dic setValue:@"" forKey:@"errorMsg"];
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:dic options:0 error:nil];
NSString * jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSString * jsCall = [NSString stringWithFormat:@"%@('%@')", callbackFunc, jsonString];
return jsCall;
}
自定义一个类,实现<WKScriptMessageHandler>协议。
#import <Foundation/Foundation.h>
@interface TCXWebViewScriptMessageDelegate : NSObject<WKScriptMessageHandler>
@property (nonatomic, weak) id<WKScriptMessageHandler> scriptDelegate;
- (instancetype**)initWithDelegate(id<WKScriptMessageHandler>)scriptDelegate;
@end
@implementation TCXWebViewScriptMessageDelegate
-(void)dealloc{
NSLog(@"THKWebViewScriptMessageDelegate dealloc");
}
- (instancetype)initWithDelegate:(id<WKScriptMessageHandler>)scriptDelegate {
self = [super init];
if (self) {
_scriptDelegate = scriptDelegate;
}
return self;
}
#pragma mark - WKScriptMessageHandler
//遵循WKScriptMessageHandler协议,必须实现如下方法,然后把方法向外传递
//通过接收JS传出消息的name进行捕捉的回调方法
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
if ([self.scriptDelegate respondsToSelector: @selector(userContentController:didReceiveScriptMessage:)]) {
[self.scriptDelegate userContentController:userContentController didReceiveScriptMessage:message];
}
}
@end