WKWebView-WKScriptMessageHandler实际应用(二)
项目中代码
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ if ([message.name isEqualToString:@"TCXJSbridge"]) { //解析数据,执行相应的代码 NSString *actionName = body[@"name"]; if (@"map.getLocation" isEqualToString:actionName]){ //经纬度业务 }else if (@"user.getInfomation" isEqualToString:actionName]){ //用户信息业务 }else{ //其他... } } }复制代码
问题分析
随着项目越来越大,WebView与HTML的交互越来越多,就会暴露新的问题。
这样写代码会存在什么问题呢?
VC代码庞大;
代码中太多的if...else了,可维护性差;
团队开发过程中,同时修改一个文件容易产生冲突;
可复用性低。其他VC使用时,只能copy,大大提高了后期的维护成本。
解决方案:
代码放到工具类或者viewModel中
虽然VC的代码少了,但是viewModel中还是存在大量的if...else代码,而且viewModel中会有UIKit的存在,这样写有点不优雅。
通过面向协议开发
实现结耦、团队多人同时开发。
单一职责,每个JS交互都对应一个类,负责自己的业务逻辑。
代码优化
分析共性
初始化方法
接收参数
处理业务逻辑
回调数据给HTML页面
优化后的代码
思路:利用runtime和面向协议编程思想
1、jS交互的名称和实际类名关联起来
2、定一个协议,声明协议方法,判断是否能处理这个JS交互、处理逻辑的方法、创建类的方法
3、通过JS传过来的数据找到类名、利用runtime生成处理JS的model
4、然后再执行处理的协议方法。
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{ if ([@"TCXJSbridge" isEqualToString:message.name]) { //native 和 js 交互 NSDictionary *body = message.body; if (!body) { return; } if ([body isKindOfClass:[NSDictionary class]]) { NSString *actionName = body[@"name"]; @weakify(self); TWebActionCallback callback = ^(TWebActionCallbackData * _Nonnull data) { @strongify(self) if(data.jsCallback){ [self.webView evaluateJavaScript:data.jsCallback completionHandler:^(id _Nullable object, NSError * _Nullable error) {}]; } }; //JS传递参数封装成对象 TWebAction *webAction = [TWebAction webActionFromActionName:actionName runtimeParam:body[@"data"] callback:callback]; webAction.callbackFunc = body[@"callback"]; //创建具体处理业务的target id<TWebActionProtocol> target = [[TWebActionManager sharedManager] createTargetWithWebAction:webAction]; if (!target) { TWebActionCallbackData *callModel = [[TWebActionCallbackData alloc] init]; callModel.status = 404; callModel.jsCallbackFunc = webAction.callbackFunc; callModel.actionName = webAction.actionName; callModel.errorMsg = @"请升级至最新版本"; webAction.webActionCallback(callModel); return; } //处理实际业务 if([target respondsToSelector:@selector(handleWebAction:withHandler:)]){ [target handleWebAction:webAction withHandler:self]; } } } }复制代码
定义协议
@protocol TWebActionProtocol <NSObject> /** 是否能处理这个操作 @param actionName 操作名称 @return YES/NO */ + (BOOL)canHandleWebAction:(NSString*)actionName; /** 处理这个操作 @param handler handler */ - (void)handleWebAction:(TWebAction*)webAction withHandler:(id)handler; /** 创建具体的实例 @return id */ + (id)createTargetWithWebAction:(TWebAction*)webAction;复制代码
TWebActionManager核心代码,在实现了TWebActionProtocol协议的数组里,根据名称查找,并创建对象
#pragma mark - 创建具体的webAction实现类 /** 创建对象 @param webAction 操作类 @return 实现协议的对象 */ -(id<TWebActionProtocol>)createTargetWithWebAction:(TWebAction*)webAction{ if (!webAction) { return nil; } Class class = [self findClassForWebAction:webAction]; if (!class) { return nil; } return [self createTargetForWebAction:webAction withClass:class]; } /** 根据webAction查找对应的类名 @param webAction 操作类 @return class */ - (Class)findClassForWebAction:(TWebAction *)webAction { NSString *actionName = webAction.actionName; if (!actionName || ![actionName isKindOfClass:[NSString class]] || actionName.length == 0) { return nil; } Class cacheClass = [self.classMapCache objectForKey:webAction.actionName]; if (cacheClass) { //已经缓存的类型,可以直接跳转 return cacheClass; } else { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-method-access" //未缓存的,先判断该种类型的跳转能被哪个类响应,并缓存下来,再执行跳转 for (Class<TWebActionProtocol> webActionClass in self.webActionClasses) { BOOL canHandle = [webActionClass performSelector:@selector(canHandleWebAction:) withObject:webAction.actionName]; if (canHandle) { [self.classMapCache setObject:webActionClass forKey:webAction.actionName]; return webActionClass; } } #pragma clang diagnostic pop } return nil; } /** 根据webAction创建具体的类 @param webAction 操作类 @param webActionTargetClass 目标类的class @return 具体的类 */ - (id)createTargetForWebAction:(TWebAction *)webAction withClass:(Class)webActionTargetClass { id target; //创建具体的类 if ([webActionTargetClass respondsToSelector:@selector(createTargetWithWebAction:)]) { target = [webActionTargetClass performSelector:@selector(createTargetWithWebAction:) withObject:webAction]; }else{ return nil; } //设置参数 if (target){ [webAction setValuesForObject:target]; } //设置callback if (webAction.webActionCallback && [target isKindOfClass:[NSObject class]]) { ((NSObject *)target).webActionCallback = webAction.webActionCallback; webAction.webActionCallback = nil; } return target; }复制代码
举个例子
以获取经纬度为例
1、新建一个类,继承TCXWebActionModel实现TWebActionProtocol协议。继承的目的是一些通用的处理实现可以放在基类。例如初始化等。
@interface TCXWebGetLocation : TCXWebActionModel<TWebActionProtocol> @end复制代码
2、实现协议方法。
3、handleWebAction:withHandler:方法实现具体业务逻辑。并回调到VC,VC执行JS代码。
@implementation THKWebGetLocation + (BOOL)canHandleWebAction:(NSString *)actionName { if ([@"map.getLocation" isEqualToString:actionName]) { return YES; } return NO; } - (void)handleWebAction:(TWebAction*)webAction withHandler:(id)handler { TWebActionCallbackData *model = [self successCallbackData]; double lg = 116.397128; double lat = 39.916527; NSMutableDictionary * mutableDict = [NSMutableDictionary dictionaryWithCapacity:2]; [mutableDict setValue:@(lat) forKey:@"latitude"] [mutableDict setValue:@(lg) forKey:@"longitude"]; model.data = mutableDict; self.webActionCallback(model); } @end
作者:龙在掘金62077
链接:https://juejin.cn/post/7015211645290938399