小工具      在线工具  汉语词典  css  js  c++  java

JavaScript 和 Objective-C 交互

IOS 额外说明

收录于:97天前

转载:http://www.jianshu.com/p/f896d73c670a

注:本文仅推荐给需要适配iOS7的同学。如果你已经扔掉了iOS7,强烈建议使用WKWebView代替。 WKWebView文章发表WKWebView(keng)的使用及注意事项

最近公司的运营瞎搞了个活动,其活动要服务端提供数据支持,web前端在微信公众账号内作为主要的运营阵地,而iOSAndroid要提供相应的入口及页面进行配合。一个活动,动用了各个端的程序猿。而在这里面技术方面主要就是涉及到web端和服务端的交互,web前端iOSAndroid的交互。本人作为一个iOS开发者,今天就聊聊webiOSAndroid三端的交互,其实在说明白一点就是方法的互相调用而已。这里主要讲解iOSAndroid会稍微提一下,仅作参考。

本文逻辑图


图0-0 本文逻辑图

概述

iOS原生应用和web页面的交互大致上有这几种方法iOS7之后的JavaScriptCore拦截协议第三方框架WebViewJavaScriptBridgeiOS8之后的WKWebView在这里主要讲解JavaScriptCore拦截协议这两种办法。WebViewJavaScriptBridge是基于拦截协议进行的封装。学习成本相对JavaScriptCore较高,使用也不如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
相关应用

用这些方法去执行大段的JavaScript代码是没什么必要的,但是有些小场景用起来还是比较顺手和实用的,列举两个例子作为参考:

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

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

JavaScript 核心

iOS7之后苹果推出了JavaScriptCore这个框架,从而让web页面和本地原生应用交互起来非常方便,而且使用此框架可以做到Android那边和iOS相对统一,web前端写一套代码就可以适配客户端的两个平台,从而减少了web前端的工作量。

网页前端

在三端交互中,web前端要强势一些,一切传值、方法命名都按web前端开发人员来定义,让另外两端去做适配。在这里以调用摄像头和分享为例来详细讲解,测试网页代码取名为test.html,其代码内容如下:

test.html代码内容
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
</head>
<body>
    <div style="margin-top: 100px">
        <h1>Objective-C和JavaScript交互的那些事</h1>
        <input type="button" value="CallCamera" onclick="Toyun.callCamera()">
    </div>       

    <div>
        <input type="button" value="Share" onclick="callShare()">
    </div>

<script>
    var callShare = function() {
        var shareInfo = JSON.stringify({"title": "标题", "desc": "内容", "shareUrl": "http://www.jianshu.com/p/f896d73c670a",
        "shareIco":"http://upload-images.jianshu.io/upload_images/1192353-fd26211d54aea8a9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"});
        Toyun.share(shareInfo);
    }

    var picCallback = function(photos) {
        alert(photos);
    }

    var shareCallback = function(){
        alert('success');
    }
</script>
</body>
</html>
test.html代码解释

可能有些同学对web前端的一些知识不太熟悉,稍微对这段代码做下解释,先说ToyuniOSAndroid这两边在本地要注入的一个对象【参考下面iOS的代码更容易明白】,充当原生应用和web页面之间的一个桥梁。页面上定义了两个按钮名字分别为CallCameraShare。点击CallCamera会通过Toyun这个桥梁调用本地应用的方法- (void)callCamera,没有传参;而点击Share会先调用本文件中的JavaScript方法callShare这里将要分享的内容格式转成JSON字符串格式(这样做是为了适配AndroidiOS可以直接接受JSON对象)然后再通过Toyun这个桥梁去调用原生应用的- (void)share:(NSString *)shareInfo方法这个是有传参的,参数为shareInfo。而下面的两个方法为原生方法调用后的回调方法,其中picCallback为获取图片成功的回调方法,并且传回拿到的图片photosshareCallback为分享成功的回调方法。

iOS系统

iOS这边根据前端定义的方法名来写代码,但是有些时候web前端会让我们定义,但是我们定义好之后他又要修改,这时候就会很烦啊。所以碰到三端交互的时候最好就是让web前端去定义方法名,iOSAndroid根据web前端定义好的去写代码。JavaScriptCoreweb页面调用原生应用的方法可以用DelegateBlock两种方法,此文以按Delegate讲解。

JavaScriptCore 中的类和协议:
  • JSContext:给JavaScript提供运行的上下文环境
  • JSValue:JavaScriptObjective-C数据和方法的桥梁
  • JSManagedValue:管理数据和方法的类
  • JSVirtualMachine:处理线程相关,很少使用
  • JSExport:这是一个协议。如果您使用协议方法进行交互,则您定义的协议必须遵守该协议。
ViewController 中的代码
#import "ViewController.h"
#import <JavaScriptCore/JavaScriptCore.h>

@protocol JSObjcDelegate <JSExport>

- (void)callCamera;
- (void)share:(NSString *)shareString;

@end

@interface ViewController () <UIWebViewDelegate, JSObjcDelegate>

@property (nonatomic, strong) JSContext *jsContext;
@property (weak, nonatomic) IBOutlet UIWebView *webView;

@end

@implementation ViewController

#pragma mark - Life Circle

- (void)viewDidLoad {
    [super viewDidLoad];

    NSURL *url = [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"html"];
    [self.webView loadRequest:[[NSURLRequest alloc] initWithURL:url]];

}

#pragma mark - UIWebViewDelegate

- (void)webViewDidFinishLoad:(UIWebView *)webView {
    self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    self.jsContext[@"Toyun"] = self;
    self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
        context.exception = exceptionValue;
        NSLog(@"异常信息:%@", exceptionValue);
    };
}

#pragma mark - JSObjcDelegate

- (void)callCamera {
    NSLog(@"callCamera");
    // 获取到照片之后在回调js的方法picCallback把图片传出去
    JSValue *picCallback = self.jsContext[@"picCallback"];
    [picCallback callWithArguments:@[@"photos"]];
}

- (void)share:(NSString *)shareString {
    NSLog(@"share:%@", shareString);
    // 分享成功回调js的方法shareCallback
    JSValue *shareCallback = self.jsContext[@"shareCallback"];
    [shareCallback callWithArguments:nil];
}

@end
ViewController中的代码解释

自定义JSObjcDelegate协议,而且此协议必须遵守JSExport这个协议,自定义协议中的方法就是暴露给web页面的方法。在webView加载完毕的时候获取JavaScript运行的上下文环境,然后再注入桥梁对象名为Toyun,承载的对象为self即为此控制器,控制器遵守此自定义协议实现协议中对应的方法。在JavaScript调用完本地应用的方法做完相对应的事情之后,又回调了JavaScript中对应的方法,从而实现了web页面本地应用之间的通讯。

JavaScriptCore 使用注意事项

JavaScript调用本地方法是在子线程中执行的,这里要根据实际情况考虑线程之间的切换,而在回调JavaScript方法的时候最好是在刚开始调用此方法的线程中去执行那段JavaScript方法的代码,我在实际运用中开始没注意,就被坑惨了啊。什么,说的太绕,看下面的代码解释:

//  假设此方法是在子线程中执行的,线程名sub-thread
- (void)callCamera {     
    // 这句假设要在主线程中执行,线程名main-thread
    NSLog(@"callCamera");  

    // 下面这两句代码最好还是要在子线程sub-thread中执行啊
    JSValue *picCallback = self.jsContext[@"picCallback"];
    [picCallback callWithArguments:@[@"photos"]];
}
运算结果

运行效果如图3-1


图3-1 运行效果

拦截协议

拦截协议这个适合一些比较简单的一些情况,不需要引入什么框架,只需要web前端配合一下就好。但是在具体调用哪一个方法上,以及在传值的时候可能会有些不方便,而且调用完后无法在回调JavaScript的方法。

网页前端
test.html 中的代码
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
</head>
<body>
    <div>
        <input type="button" value="CallCamera" onclick="callCamera()">
    </div>

<script>
    function callCamera() {
        window.location.href = 'toyun://callCamera';
    }
</script>
</body>
</html>
test.html中的代码解释

这段代码相比上面的那段测试代码是很简单的,同样有一个按钮,名字为CallCamera点击之后调用自己的callCamera方法,window.location.href这里是改变主窗口的指向从而马上发出一个链接为toyun://callCamera请求,而想要传给原生应用的参数也可已包含到此请求中,而在iOS方法中我们要拦截这个请求,根据请求内容去判断JavaScript想要做的事情,从而实现web页面本地应用之间的交互。

iOS系统
iOS对应代码
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSString *url = request.URL.absoluteString;
    if ([url rangeOfString:@"toyun://"].location != NSNotFound) { 
        // url的协议头是toyun
        NSLog(@"callCamera");
        return NO;
    }
    return YES;
}
iOS对应代码说明

webView的代理方法中去拦截自定义的协议Toyun://如果是此协议则据此判断JavaScript想要做的事情,调用原生应用的方法,这些都是提前约定好的,同时阻止此链接的跳转。

总结

随着手机硬件的配置越来越强大和HTML5的兴起,一个App完全可以由web页面来写。现在已经有部分应用这么干了,我是遇见过的,如古诗文网。尽管比较少但是web页面本地应用的交互不论是iOS还是Android都是会有遇到的。iOS我还是比较推荐JavaScriptCore,这样三端可以相对统一起来,写的时候都比较简单。随着时间的推移iOS8推出的WKWebView会逐渐成为主流,这个的功能更强大。拦截协议也只能说用到比较简单的一些情况吧,复杂的情况处理相互之间参数的传递还是比较麻烦的,而且这个不能回调JavaScript的方法,确实喜欢拦截协议的同学可以研究WebViewJavaScriptBridge这个第三方库。对于Android本人也就是略知皮毛而已,就不班门弄斧了,对于一些Android开发者来说,可以看地第一段的test.html这个页面的写法完全是可以适配Android的。

更新

关于使用过程中的坑,出了一片续,具体参看关于 JavaScript 和 Objective-C 交互的须知事项(续)

关于WKWebView,已经出了一篇新文章,具体参看WKWebView(keng)的使用及注意事项

参考

. . .

相关推荐

额外说明

mybatispuls 批处理 rewriteBatchedStatements=true

mybatis-plus原生的批处理   this.saveBatch(list); 实际是一条条处理,特慢,造几万行数据得几分钟以上。 如果加上配置,就十几秒搞定五万行数据入库 &rewriteBatchedStatements=true      

额外说明

学习常见的SQL查询优化技术

《SQL 从入门到精通》专栏目录 第 01 篇 和数据打交道的你,一定要学会 SQL 第 02 篇 在 SQL 的世界里一切都是关系 第 03 篇 使用 SELECT 语句初步探索数据库 第 04 篇 通过查询条件实现数据过滤 第 05 篇 如何使用 S

额外说明

恶意代码分析实战——vmware--->Dll注入恶意分析

QQ 1274510382 Wechat JNZ_aming 商业联盟 QQ群538250800 技术搞事 QQ群599020441 解决方案 QQ群152889761 加入我们 QQ群649347320 共享学习 QQ群674240731 纪年科技am

额外说明

量化交易 第四课 回测交易接口

第四课 回测交易接口 概述 交易函数 指定股票交易 目标价值下单 交易注意事项 拒单 市价 vs 限价 交易的费用 投资组合 查看投资组合信息 context 属性 portfolio 对象 代码实现 概述 在建立自己的量化策略回测系统之前, 我们必须对

额外说明

常见的http请求参数和响应参数,前后端交互参数说明

概念:Hyper Text Transfer Protocol 超文本传输协议 传输协议:定义了客户端和服务器端通信时,发送数据的格式。 特点: 基于TCP/IP的高级协议 默认端口号:80 基于请求/响应模型的:一次请求对应一次响应 无状态的:每次请求

额外说明

jQuery(五)Ajax、跨域

目录 一、Ajax 二、跨域 一、Ajax $.ajax({ url:"服务器端接口地址", type:"get或post", //请求类型 data:{ //如果没有参数,可省略 参数名: 参数值, ... : ...

额外说明

Hasor【环境搭建 03】Dataway接口配置服务使用DataQL聚合查询引擎(SQL执行器实现分页查询举例说明+报错 Query dialect missing 原因分析及解决)

1.报错说明 在本地搭建了两个平台,hasor核心依赖的版本是一致的, 连接的都是GreenPlum数据库 ,且执行的是相同的DataQL语句: <!--hasor核心依赖【是老平台接入,由于兼容问题,最终用的并不是最新的4.2.5版本】

额外说明

解决Windows系统应用程序启动时出现缺少comcat.dll文件的问题

其实很多用户玩单机游戏或者安装软件的时候就出现过这种问题,如果是新手第一时间会认为是软件或游戏出错了,其实并不是这样,其主要原因就是你电脑系统的该dll文件丢失了或没有安装一些系统软件平台所需要的动态链接库,这时你可以下载这个comcat.dll文件(挑

ads via 小工具