阅读说明:本文不讲解JavaScriptCore 基本使用。网上博客比较多,看几篇基本都会使用了。这里只针对使用过程中遇到的一些问题。以便更好的使用JavaScriptCore。
由于开发的项目是电商项目,涉及到很多UIWebView和HTML的交互。对他们的交互可以说比较熟悉了。一路走来走了,遇到了不少坑,为了大家少走弯路!以此记录使用JavaScriptCore过程中遇到的问题。
之前一直用的是WebViewJavascriptBridge,但由于一下几个原因我决定放弃它:
1.由于需要在HTML中添加代码,不能和安卓公用一套代码
2.iOS 10 中(4.0.4版本崩溃),必须升级。一些在服务端的页面无法跟着升级(会造成线上用户不能响应H5)。
3.JavaScriptCore 使用简单。
JavaScriptCore:(本文重点)
1.JavaScriptSore是苹果在iOS7之后提供的一套框架,它让JS与OC的交互更加简单方便。
2.JavaScriptSore是一套完善的框架,它能满足两者交互的各种需要。
3.使用步骤:
a:导入头文件 #import <JavaScriptCore/JavaScriptCore.h>
b:在.m 中声明一个全局(带下划线)的对象:JSContext *_jsContext;
c:在代理方法中创建对象:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
if (!_jsContext) {
_jsContext = [m_webviewLivingGoodsDetail valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
_jsContext[@"wst"] = self;// 把控制器给html。以便html中直接调用OC中的方法。wst 双方约定。
}
// 调试 Xcode 控制台打印html中的log。
_jsContext[@"console"][@"log"] = ^(id temObj){
NSLog(@"temObj-------:%@",temObj);
};
// 2. 关联打印异常
_jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
context.exception = exceptionValue;
NSLog(@"异常信息:%@", exceptionValue);
};
// JavaScript->OC
// 所有jsCallApp 事件都在这里注册好。
// 分享。jsCallApp2Share是OC 的方法。方法名双方约定。
__weak typeof(self)weakSelf = self; //
_jsContext[@"window"][@"wst"][@"jsCallApp2Share"] = ^(id data) {
NSLog(@"jsCallApp2Share called: %@", data);
[weakSelf performSelectorOnMainThread:@selector(didCallApp2Share) withObject:nil waitUntilDone:NO];
};
}
html 中调用OC的方法
// 分享方法
function jsCallApp2Share(){
try{
window.wst.jsCallApp2Share();
}catch(error){
console.log(error.message);
}
}
d:OC->JavaScript(这里两个参数)
// 将dictionaryGoodsDetail字典转换为jsonStr
+ (NSString *)transformDictionaryToJsonStr:(NSDictionary *)dic;
{
if ([NSJSONSerialization isValidJSONObject:dic]) {
NSError *parseError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic options:0 error:&parseError];
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
} else {
return nil;
}
}
方法一:需要将dictionaryGoodsDetail字典转换为jsonStr
NSString *jsonStr = [GeneralUtils transformDictionaryToJsonStr:dictionaryGoodsDetail];
方法二:
__strong NSString *temStrWeak = methodString;
dispatch_async(dispatch_get_main_queue(), ^{
[webview stringByEvaluatingJavaScriptFromString:temStrWeak];
});
方法三:此方法直接将请求数据塞给html。(推荐方法三)
JSValue *temfunction = _jsContext[@"appCallJsGetGoodsDetail"];
[temfunction callWithArguments:@[dictionaryGoodsDetail,@"0"]];
+ (NSString *)transformSingleSlashToDoubleSlashWithJsonStr:(NSString *)jsonStr
{
NSString *singleStr = @"\\"; // 单斜杠Str
NSString *doubleStr = [NSString stringWithFormat:@"%@%@",@"\\",@"\\"]; // 双斜杠Str
NSString *resultStr = [jsonStr stringByReplacingOccurrencesOfString:singleStr withString:doubleStr];
return resultStr;
}
// 将“\” 换成“\\”
jsonStr = [GeneralUtils transformSingleSlashToDoubleSlashWithJsonStr:jsonStr];
这样就好了。但是这种做法维护性太差。
问题2:问题一中以字符串传,让html解析,维护性太差。直接传字典才是王道啊。于是就找到了JavaScriptCore的另一个方法:callWithArguments:这个方法可以不用转字符串。直接传字典。
替换后发现了一个问题多次进入这个webview 会崩溃,活着页面卡死。
这样做,html页面偶尔能出来,更多时候会崩溃:
崩溃1.WebCore`WebCore::RenderStyle
崩溃2.- (JSValue *)callWithArguments:(NSArray *)arguments 偶尔崩到这里。
基本都是
一是: exc_bad_access code=1 address=**** 另一个是: exc_breakpoint (code=exc_i386_bpt subcode=0x0) .
根据上面崩溃:总感觉是webview的渲染问题。
根据崩溃2:找到
解决了崩溃。就是在html中加了延迟执行。终于不会崩溃。心情舒畅了许多!
但是:在html 中的函数中加个延迟函数。
setTimeout(function(){// 执行代码},650);
思考:这个延时安卓端又不需要,多少又修改了html的代码。这是我最不想做的事。
根据崩溃1:
http://www.cnblogs.com/hui314/p/IOS_javascriptcore_notice.html#undefined 感谢这个文章的作者。必要贴一下文章内容。
IOS7--javascriptcore中jscontext使用要注意的一点
在公司一个项目中,用到了highchart做图表显示的组件,这就要用到了javascriptcore,代码就不上了,说说原理。
需求是这样的,通过http请求server csv格式的数据,然后解析,最后传入LOCAL的html 中用highchart显示出来。
由于需要显示loading,progress等,所以就用了IOS提供的原生NSURLConnection,实现 NSURLConnectionDelegate 和 NSURLConnectionDataDelegate .
原先的设计是这样的,在controller view实现 <UIWebViewDelegate> ,在viewDidLoad 中加载本地的html文件(这个就是显示chart的html,里面配置好chart所需要的一切,只等OC传数据进来),在 - webViewDidFinishLoad: 中初始化JSContext并开始连接请求. 在 - connectionDidFinishLoading: 把数据痛过[JSContext[@"value" callWithArguments:] ]传给对用的js method。
这样做,Chart偶尔能出来,更多时候throw exception。exception有两个,一是: exc_bad_access code=1 address=**** 另一个是: exc_breakpoint (code=exc_i386_bpt subcode=0x0) .
开始怀疑是数据的问题,就把网络请求到的数据储存到本地,然后在- webViewDidFinishLoad: 通过JSContext传给js,结果完全没有问题,chart每次显示完美。
通过搜索,在stackoverflow中了解到报错:exc_bad_access code=1 address=**** 的原因是,使用了已经release的object。通过Debug,跟踪到出问题时候是在调用js method的时候。因为项目用的是ARC,而我们的服务器是放在AWS上面的,并且注册的region是Ireland,服务器响应时间一般在2~5s,(比较慢,因为后台需要计算),我想,是不是在等待的这段时间,OC已经把我的JSContext release了,因为我是在 - webViewDidFinishLoad: 中初始化JSContext对象并开始请求数据的,也就是说,JSContext对象至少闲置了2~5s的时间。
开始修改代码,在 viewdidload 中首先开始请求数据,等请求完成后在开始load local html,然后在- webViewDidFinishLoad: 再初始化JSContext,并把数据传过去[JSContext[@"value" callWithArguments:] ] ,run ... 一切没问题。
ARC的确是方便了我们很多,也许我们在享受它带来好处的同时也要警惕它最大的好处。
于是就按照文章说的。
1.在请求接口成功返回渲染的数据后,再去加载html[self loadWebView];
- (void)loadWebView
{
// 载入webview绘制的界面
NSString *resourcePath = [[NSBundle mainBundle] resourcePath];
NSString *filePath = [resourcePath stringByAppendingPathComponent:@"/html/detail.html"];
NSString *htmlstring = [[NSString alloc]initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
[webView loadHTMLString:htmlstring baseURL:[NSURL fileURLWithPath:filePath]];
}
2.
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
// dictionaryGoodsDetail为接口返回的数据
JSValue *temfunction = _jsContext[@"appCallJsGetGoodsDetail"];
[temfunction callWithArguments:@[dictionaryGoodsDetail,@"0"]];
}万事大吉了!
好习惯:实现-del loc{}方法。何时释放心里清楚。
到此:对于UIWebView和HTML的交互能做到和安卓公用一套html。JavaScriptCore使用也很简单。更多方法还有待验证。也可以对
JavaScriptCore进行一层封装。
感谢阅读。