(0006) iOS 开发之JavaScriptCore 实现UIWebView和HTML的交互

阅读说明:本文不讲解JavaScriptCore 基本使用。网上博客比较多,看几篇基本都会使用了。这里只针对使用过程中遇到的一些问题。以便更好的使用JavaScriptCore。

由于开发的项目是电商项目,涉及到很多UIWebView和HTML的交互。对他们的交互可以说比较熟悉了。一路走来走了,遇到了不少坑,为了大家少走弯路!以此记录使用JavaScriptCore过程中遇到的问题。


WebViewJavascriptBridge(放弃)

之前一直用的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];

             NSString *methodString =           [NSString stringWithFormat:@"appCallJsGetGoodsDetail('%@','%@')",jsonStr,@"0"];
      [_jsContext evaluateScript:methodString];

方法二:

     __strong NSString *temStrWeak = methodString;

     dispatch_async(dispatch_get_main_queue(), ^{

          [webview stringByEvaluatingJavaScriptFromString:temStrWeak];

     });


方法三:此方法直接将请求数据塞给html。(推荐方法三)

    JSValue *temfunction = _jsContext[@"appCallJsGetGoodsDetail"];

    [temfunction callWithArguments:@[dictionaryGoodsDetail,@"0"]];



使用已经介绍完了。下面贴几个遇到的问题。

问题1:
使用方法1和2。需要 Dictionary转化为String。遇到一种情况是页面加载不出来:
:即返回的json中有换行,转化为 String中是“\n”。这个原因造成html无法正常解析。需要把“\”替换为“\\”。然后作为参数传过去。

+ (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:找到

根据这篇文章:http://stackoverflow.com/questions/28406952/jsvalue-callwithargumentsnsarray-arguments-crashed-in-ios7-0-4/40125008#40125008

解决了崩溃。就是在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进行一层封装。

感谢阅读。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值