小知识,大挑战!本文正在参与“ 程序员必备小知识 ”创作活动
本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金
通过问题看本质!!!
项目中代码
- (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