一个解析模板引擎

背景

最近老板要求 APP 改版,改的和 web 版的一模一样,期限一个周。听到这个消息后我整个人都 SB 啦。根本不可能嘛~后来技术总监说先开始做吧,能做多少做多少。然后我就想到了既然要求做的和 web 版的一模一样,那就干脆把 web 版的拿来用不就是了么。于是就直接使用 UIWebView 来加载 HTML 了。考虑到网络和流量的问题,我使用了加载本地 HTML 的方式。但是 HTML 并不是纯静态的,有些东西是需要根据不同的用户显示不同的内容的。于是就使用了 github 上的一个WebViewJavascriptBridge开源库。

初期思路

大体思路就是首先通过本地请求服务器的 json 数据,请求到之后把 json 绑定到本地的 HTML 文件中。这个绑定的过程开始的思路是使用 WebViewJavascriptBridge 把请求到的 NSDictionary 传到 JS 里面,然后通过 jquery 来修改 DOM。由于时间比较急,而且web 端的 HTML 也确实使用了 jquery,也就是说我其实不需要额外添加 JS 库,于是就使用这种方式去实现了,效果也还可以。

后来项目结束了之后我又思考了一下这种方式,感觉还是比较麻烦的一种绑定,首先请求到数据之后需要把 OC 的对象转化成 JS 对象传入到 JS 里面,然后还需要通过 OC 调用 JS 的方法去操作 DOM 改变 HTML 标签的值。从效率方面来说我觉得还是无法让我满意。

改进

后来就想到了之前做 WordPress 的时候有一种模板绑定的操作,前端人员写 HTML 代码的时候只需要使用特定的标签,就可以把相应的内容输出到这个位置。如果使用这种方式,从服务器请求到 json 数据之后,只需要把 HTML 模板内相应标签里的数据替换成真是数据就好了。这样比之前的方式要方便许多,甚至也不要你会写 JS 代码。

所做就做,首先定义好 HTML 模板的书写规则,解析引擎会把<% key %>里的内容解析成实际的值。

我是通过 NSString category 实现的,代码如下:

- (NSString *)replaceKeyWithDic:(NSDictionary *)dict callBack:(HTMLReplaceBlock)cb{
NSMutableString *tempString = [NSMutableString stringWithString:self];
NSString *regulaStr = @"<% .*? %>";
NSError *error = nil;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:regulaStr
options:NSRegularExpressionCaseInsensitive
error:&error];
NSArray *arrayOfAllMatches = [regex matchesInString:tempString options:0 range:NSMakeRange(0, [tempString length])];
NSString *substringForMatch = nil;
NSInteger locationOffset = 0;//替换后range会发生偏移,该值用于计算偏移
for (NSTextCheckingResult *match in arrayOfAllMatches){
NSRange replaceRange = NSMakeRange(match.range.location+locationOffset, match.range.length);
substringForMatch = [tempString substringWithRange:replaceRange];
NSString *key = [substringForMatch substringWithRange:NSMakeRange(3, substringForMatch.length-6)];
NSString *value = nil;
if (cb) {
value = cb(key);
}
if (!value || [value isEqual:[NSNull null]]) {
value = dict[key];
}
if (!value || [value isEqual:[NSNull null]]) {
value = @"";
}
[tempString replaceCharactersInRange:replaceRange withString:value];
locationOffset += (value.length-substringForMatch.length);
}
return tempString;
}

说明:

  • 函数需要给定一个 dictionary,会把<% key %>标签解析成 dict[key]的值。
  • 然后还有一个可选的 callback,可以通过实现这个 callback 来手动赋值,这个主要用于需要逻辑判断的地方。
  • 如果 callback 返回 nil 那么就默认是 dict[key]的值

总结

使用这种方式来解析 HTML 模板实际上也是 VC 分离的好办法,这里 HTML 相当于 view,在 view 里只需要通过标签就可以引入相应的值,比起适用 JS 来操作要方便很多。

这只是一个简单的模板替换,实际上在 PHP 中的模板解析还可以做一些逻辑判断等工作,但是 objective-c 是一门静态语言,如果要在模板里面写入逻辑操作语句就需要专门对模板进行解释执行,我不知道 OC 是否可以实现。但是这里有一个 callback,需要逻辑判断的时候你可以写在 callback 里面,虽然这样会使 view 的逻辑写到 controller 里,但是我能想到的也只有这个办法了。。。

我已经把源码整合到我的 iOSHelper 项目里面去了,文件名叫NSString+HTMLReplace,你可以通过下面的地址下载它:

下载地址:iOSHelper