您的位置:首页 > 移动开发

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

2016-10-20 14:09 573 查看
阅读说明:本文不讲解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进行一层封装。

感谢阅读。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: