iOS OC与JS的交互(JavaScriptCore实现)

随着中秋国庆的到来,公司的运营要搞一系列活动,这就需要服务端提供数据支持,iOS、Android要提供相应的入口及页面进行配合,实现邀请好友分享得奖励的功能。这里面涉及到web端和服务端的交互,web端和iOS、Android的交互,在这仅仅学习下iOS和web的交互。

iOS原生应用和web页面的交互有iOS7之后的JavaScriptCore、拦截协议、第三方框架WebViewJavaScriptBridge、iOS8之后的WKWebView几种方法,这一章我们主要讲解JavaScriptCore和拦截协议这两种办法。WebViewJavaScriptBridge是基于拦截协议进行的封装,使用也不如JavaScriptCore方便本文不做细讲。WKWebView是iOS8之后推出的,还没有成为主流使用,所以本篇文章也不做详细叙述。

Objective-C调用JavaScript代码

// UIWebView的方法
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

// JavaScriptCore中JSContext的方法
- (JSValue *)evaluateScript:(NSString *)script;
- (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL *)sourceURL

用这些方法执行复杂的一大段js代码也是没有必要的,在一些场景下还是比较实用的,比如

// 获取当前页面的title
NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"];

// 获取当前页面的url
NSString *url = [webview stringByEvaluatingJavaScriptFromString:@"document.location.href"];

JavaScriptCore概述
JavaScriptCore这个框架是iOS7之后苹果推出的,方便了开发者的使用,让web页面和iOS本地原生应用交互起来更加简单。

web前端
在与前端交互过程中,需要与前端开发人员沟通好传值、方法名等,然后移动端做适配。这里以传值、调用本地alert、分享为例来讲解,用webView加载HTML文件,这里用的是本地HTML名字为JavaScriptCore.html,代码如下:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title> 来自html中的jsCallOC标题</title>
</head>
<body>
<div style="margin-top: 20px">
<h2>JavaScript与OC的交互</h2>
<input type="button" value="Native传值" onclick="Native.callme('jS开始调用OC本地Native咯')">
</div>
<div>
<input type="button" value="oc原生Alert" onclick="deliverValue('来自HTML中的Alert信息')">
</div>
<div>
<input type="button" value="Share"   onclick="callShare()">
</div>
<script>
var alertShowIn = function(str) {

    alert(str);
}
var callShare = function() {
var shareUrl = "http://image.baidu.com/search/detail?ct=503316480&z=&tn=baiduimagedetail&ipn=d&ie=utf-8&in=24401&cl=2&lm=-1&st=-1&step_word=&rn=1&cs=&ln=1998&fmq=1402900904181_R&ic=0&s=&se=1&sme=0&tab=&width=&height=&face=0&is=&istype=2&ist=&jit=&fr=ala&ala=1&alatpl=others&pos=1&pn=1&word=图片%20动漫卡通&di=0&os=1199087710,2399135616&pi=0&objurl=http%3A%2F%2Fv.flash.beijingww.com%2Fcomic%2Fwallpaper%2Fjiqimao%2F15.jpg"
Native.share(shareUrl);
}
var shareCallBack = function(){
alert('回调js分享success');
}
</script>
</body>
</html>

JavaScriptCore.html代码解释如下:

Native是iOS本地要注入的一个对象,也就是web页面与原生应用的一个桥接。页面上定义了Native传值、oc原生Alert、Share三个按钮,点击Native传值首先通过Native这个桥梁调用本地的方法- (void)callme:(NSString *)string并传入参数;点击oc原生Alert按钮通过 self.context[@"deliverValue"] = ^(NSString *message) 的block形式直接调用;点击Share按钮会先调用html本地文件中的JavaScrip的function方法callShare,这里将分享的url参数传给share方法,然后再通过Native桥梁去调用原生应用的本地方法- (void)share:(NSString *)shareUrl,而shareCallBack为分享成功的回调方法,也就是原生方法调用后js的回调方法。

iOS移动端
JavaScriptCore中web页面调用原生应用的方法可以用Delegate或Block两种方法。

JavaScriptCore中类及协议:

JSContext:给JavaScript提供运行的上下文环境
JSValue:JavaScript和Objective-C数据和方法的桥梁
JSExport:协议,如果采用协议的方法交互,自己定义的协议必须遵守此协议

ViewController中的代码

#import <JavaScriptCore/JavaScriptCore.h>
@protocol JSObjectDelegate <JSExport>
-(void)callme:(NSString *)string;
-(void)share:(NSString *)shareUrl;
@end

@interface JSCallOCViewController ()   <UIWebViewDelegate, JSObjectDelegate>
@property(nonatomic, strong) UIWebView *webView;
@property(nonatomic, strong) JSContext *context;
@end

@implementation JSCallOCViewController
-(void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.title = @"js call oc";
self.view.backgroundColor = [UIColor whiteColor];

self.webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight)];
[self.view addSubview:self.webView];

NSString *path = [[NSBundle mainBundle] pathForResource:@"JavaScriptCore" ofType:@"html"];

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]];

self.webView.delegate = self;
[self.webView loadRequest:request];
}

#pragma mark - UIWebViewDelegate

-(void)webViewDidFinishLoad:(UIWebView *)webView

{
//获取html title设置导航栏 title
self.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//捕捉异常回调
self.context.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
context.exception = exceptionValue;
NSLog(@"异常信息: %@",exceptionValue);
};

//通过JSExport协议关联Native的方法
self.context[@"Native"] = self;

//通过block形式关联JavaScript中的函数
__weak typeof(self) weakSelf = self;

self.context[@"deliverValue"] = ^(NSString *message) {
   
    __strong typeof(self) strongSelf = weakSelf;

    dispatch_async(dispatch_get_main_queue(), ^{
        
        UIAlertController *alertControl = [UIAlertController alertControllerWithTitle:@"this is a message" message:message preferredStyle:UIAlertControllerStyleActionSheet];
        UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
            
        }];
        [alertControl addAction:cancelAction];
        [strongSelf.navigationController presentViewController:alertControl animated:YES completion:nil];
    });


};

}

-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
NSLog(@"error == %@",error);
}

#pragma mark - JSExport Methods
-(void)callme:(NSString *)string
{
NSLog(@"%@",string);
}

-(void)share:(NSString *)shareUrl
{
NSLog(@"分享的url=%@",shareUrl);
JSValue *shareCallBack = self.context[@"shareCallBack"];
[shareCallBack callWithArguments:nil];
}

代码解释:
自定义JSObjectDelegate协议的时候必须遵守JSExport这个协议,这些自定义的协议中的方法是留给web页面的接口方法。在webView加载完毕的时候获取JavaScript运行的上下文环境,然后注入桥接的对象Native,对象self就是此控制器,控制器遵守此自定义协议实现协议中的相对应的方法。当JavaStript调用完原生本地应用的方法后,再回调JavaScript中对应的方法,从而实现了Web页面和原生本地应用之前的通信。

OC调用JS效果图:


屏幕快照 2016-09-22 下午3.17.26.png

JS调用OC代码

-(void)caculateButtonAction:(id)sender
{
NSNumber *inputNumber = [NSNumber numberWithInteger:[textField.text integerValue]];
JSValue *function = [self.context objectForKeyedSubscript:@"factorial"];
JSValue *result = [function callWithArguments:@[inputNumber]];

resultL.text = [NSString stringWithFormat:@"%@",[result toNumber]];

JS调用OC效果图:

屏幕快照 2016-09-22 下午3.28.39.png

ps:注意JavaStript调用本地方法是在子线程中执行的,在回调JavaStript方法的时候最好与刚开始调用此方法的线程保持同一个,要考虑线程之间的切换。

拦截协议

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<div>
    <input type="button" value="changeWindow" onclick="callMethod()">
</div>
 
<script>
function callMethod() {
    window.location.href = 'wjika://changeWindow';
}
</script>
</body>
</html>

html代码解释:
点击changeWindow按钮调用网页callMethod方法,方法的实现是window.location.href改变主窗口的指向,也就是发出一个链接为wjika://changeWindow的请求,从而将内容传递给原生应用,请求中可以传递原生应用需要的参数。在原生应用中我们可以拦截这个请求,根据内容去判断JavaStript想要我们做的事情,这就实现了web页面和原生应用本地之间的交互。

viewController中的代码

-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSString *url = request.URL.absoluteString;
if ([url rangeOfString:@"wjika://"].location != NSNotFound) { 
    // url的协议头是wjika
    NSLog(@"改变窗口指向");
    return YES;
}
return No;
}

在webView的shouldStartLoadWithRequest代理方法中去拦截自定义的协议wjika://如果是此协议则去做JavaStript想要移动端做的事情,调用原生应用的方法,注意协议的头等字段是前端和移动端事先约定好的。

ps:拦截协议适合一些简单的情况,复杂的交互需要相互传递参数的比较麻烦,并且不能回调JavaScript的方法。另外研究拦截协议的朋友可以看看WebViewJavaScriptBridge这个第三方,是对拦截协议的封装。 JavaScriptCore使用起来比较简单,方便web端和移动端的统一。iOS8推出的WKWebView会逐渐成为主流,这个功能更强大。仅供交流学习,欢迎各位同学指正。

本文Demo

参考
iOS中实现JS和OC的交互(Hybrid App)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容