最近项目中用到了JS与OC交互的,所以我就来讲一下JS与OC交互的详细过程,以及在做项目的时候遇到的问题,跟大家分享一下。
1:关于交互实现方式的选择。
网上讨论比较多的有一个第三方库WebViewJavascriptBridge,个人不建议用,因为本身我们在做H5交互的时候就是给前端增加了工作量,而这种处理方式就需要前端要配置两套代码,一套给安卓,一套给iOS,而且不利于调试。所以我最后选择用系统的JavaScriptCore框架,JavaScriptzCore内部有五个框架,分别是:
#import"JSContext.h",
#import"JSValue.h",
#import"JSManagedValue.h",
#import"JSVirtualMachine.h",
#import"JSExport.h"
本文只讲常用的JSContext和JSValue,JSExport这三个类。
(1):JSContext为JavaScript提供运行环境,通过-
(JSValue *)evaluateScript:(NSString
*)script;这个方法就可以执行一段JS的脚本代码,具体使用方法如下:
NSString *alertJS=@"alert(‘test
js OC‘)";//准备执行的js代码
[self.jsContextevaluateScript:alertJS];//通过oc方法调用js的alert
(2):JSValue是JavaScript和Native之间互换的桥梁,它提供了多种方法可以方便地把JavaScript数据类型转换成Objective-C,或者是转换过去。
Objective-c JavaScript
nil undefined //对应的参数转换关系
NSNull null
NSString string
NSNumber number,boolean
NSDictionary Object object
NSArray Array object
NSDate Date object
NSBlock Function object
id Wrapper object
Class Constructor object
方法的转换举例:
1‘)self.jsContext[@"test1"]
= ^() {
NSArray *args = [JSContextcurrentArguments];
for (id
obj in args) {
NSLog(@"测试:%@",obj);
}
};
2’)
//调用JS的方法并给JS传参数
JSValue *jsFunc2 =self.jsContext[@"getId"];//getId是JS里面的方法
PersonalSetting *s =[[PersonalSettingalloc]init];
BOOL session_nil = s.session
== nil || [s.session isEqualToString:@""];
BOOL addrID_nil = s.addr ==nil||[s.addrisEqualToString:@""];
if (addrID_nil ==YES)
{
s.addr_id =@"undefined";
}
if (session_nil ==YES){
s.session =@"undefined";
}
[jsFunc2callWithArguments:@[@{@"session":
s.session,@"addr":s.addr}]];
(3):异常处理
Objective-C的异常会在运行时被Xcode捕获,而在JSContext中执行的JavaScript如果出现异常,只会被JSContext捕获并存储在exception属性上,而不会向外抛出。时时刻刻检查JSContext对象的exception是否不为nil显然是不合适,更合理的方式是给JSContext对象设置exceptionHandler,它接受的是^(JSContext
*context, JSValue *exceptionValue)形式的Block。其默认值就是将传入的exceptionValue赋给传入的context的exception属性:
/******2.关联打印异常,由于JS的异常信息是不会在OC中被直接打印的,所以我们在这里添加打印异常信息*******/
_jsContext.exceptionHandler
= ^(JSContext *context,JSValue
*exceptionValue) {context.exception
= exceptionValue;NSLog(@"异常信息:%@",
exceptionValue);};
(4):开发步骤
1’:#import<JavaScriptCore/JavaScriptCore.h>
#import<UIKit/UIKit.h>
2‘:
@interface H5DetailVC ()<UIWebViewDelegate>
@property (nonatomic,strong)
UIWebView *webView;
@property (nonatomic,strong)
JSContext *jsContext;
@property (nonatomic,strong)
OCModel *jsocModel;
@end
3’:- (UIWebView
*)webView{
if (_webView
== nil) {
_webView = [[UIWebViewalloc]initWithFrame:CGRectMake(0,0,[UIScreenmainScreen].bounds.size.width,[UIScreenmainScreen].bounds.size.height-64)];
_webView.scalesPageToFit
= YES;
_webView.scrollView.showsVerticalScrollIndicator=NO;
_webView.scrollView.showsHorizontalScrollIndicator=NO;
_webView.scrollView.bounces=NO;
_webView.backgroundColor
= f6f6f6Color;
}return_webView;}
4‘:
-
(void)initOCModel{
self.jsContext=[[JSContextalloc]init];
self.jsocModel
=[[OCModelalloc]init];
//传导航控制器
self.jsocModel.navigationContro=(NavigationViewController
*)self.navigationController;
//传换地址的参数
self.jsocModel.ptOrderID=self.ptOrderID;
self.jsocModel.regionID=self.regionID;
self.jsocModel.title=self.shareTitle;
self.jsocModel.content=self.content;
self.jsocModel.imageURL=self.imageURL;
self.jsocModel.shareURL=self.shareURL;
//回调
__weaktypeof(self)weakSelf
= self;
self.jsocModel.block
= ^(CheckOrderControllerCellInfo *cellInfo,NSString
*tuan_id,NSString *ptOrderID){
CheckOrderController
*vc=[CheckOrderControllercreateViewController:cellInfo];
vc.tuan=tuan_id;
vc.order=ptOrderID;
__strongtypeof(weakSelf)strongSelf=weakSelf;
dispatch_async(dispatch_get_main_queue(),
^{
[strongSelf.navigationControllerpushViewController:vcanimated:YES];
});
};
}
- (void)dealloc{
NSLog(@"回收");
}
- (void)loadRequest{
NSURL *url = [NSURLURLWithString:self.h5Url];
NSURLRequest
*request = [NSURLRequestrequestWithURL:url];
[_webViewloadRequest:request];
}
- (void)backVC{
if
([_webViewcanGoBack])
{
[_webViewgoBack];
}else{
[self.navigationControllerpopViewControllerAnimated:YES];
}
}
- (void)setID{
JSValue *jsFunc2 =self.jsContext[@"getId"];
PersonalSetting
*s =[[PersonalSettingalloc]init];
BOOL session_nil = s.session
==nil || [s.session isEqualToString:@""];
BOOL
addrID_nil = s.addr ==nil||[s.addr isEqualToString:@""];
if
(addrID_nil == YES) {
s.addr =@"undefined";
}
if
(session_nil == YES){
s.session
= @"undefined";
}
[jsFunc2callWithArguments:@[@{@"session":
s.session,@"addr":s.addr_id}]];
}
#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView
*)webView{
/*************************************** 1.初始化content**********************************/
_jsContext = [webViewvalueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
/******2.关联打印异常,由于JS的异常信息是不会在OC中被直接打印的,所以我们在这里添加打印异常信息*******/
_jsContext.exceptionHandler
= ^(JSContext *context,JSValue
*exceptionValue) {
context.exception
= exceptionValue;
NSLog(@"异常信息:%@",
exceptionValue);
};
/***************************************3.初始化JSOCModel********************************/
self.jsContext[@"OCModel"]
= self.jsocModel;
self.jsocModel.jsContext
= self.jsContext;
self.jsocModel.webView
= self.webView;
// NSString *[email protected]"alert(‘test js
OC‘)"; //准备执行的js代码
// [self.jsContext evaluateScript:alertJS];//通过oc方法调用js的alert
}
- (void)webView:(UIWebView
*)webView didFailLoadWithError:(NSError *)error{
[MBProgressHUDshowError:@"加载失败"];
}
- (BOOL)webView:(UIWebView
*)webView shouldStartLoadWithRequest:(NSURLRequest *)requests navigationType:(UIWebViewNavigationType)navigationType{
returnYES;
}
(6):OCMode内部的实现
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
#import <UIKit/UIKit.h>
typedef void(^pushCheckOrderVCBlock)(CheckOrderControllerCellInfo *cellInfo,NSString *tuan_id,NSString
*ptOrderID);
@protocol JavaScriptObjectiveCDelegate <JSExport>
//跳转到登录页
- (void)pushToLoginViewController;
//获得ID
- (void)getiOSID;
//跳转到地址选择页
- (void)pushToChooseAddressVCWithParams:(NSDictionary *)params;
//分享团链接
- (void)shareTuanMethod;
//跳转到结算页
- (void)pushToCheckOrderVCWithDictonry:(NSDictionary *)params Dictionry2:(NSDictionary *)param2;
//跳转到首页的方法
- (void)pushToRootVC;
//跳转到五个人头页面
- (void)pushToFightGroupDetailVCWithPtOrderID:(NSString *)ptOrderID;
//跳转到店铺的方法
- (void)pushToShopVCWithShopID:(NSString *)shopID;
//分享红包页面
- (void)shareRedPaper;
@end
@interface OCModel :NSObject<JavaScriptObjectiveCDelegate>
@property (nonatomic,copy)pushCheckOrderVCBlock block;
@property (nonatomic,weak)JSContext *jsContext;
@property (nonatomic,weak)UIWebView *webView;
//分享的参数
@property (nonatomic,strong)NSString *title;
@property (nonatomic,strong)NSString *content;
@property (nonatomic,strong)NSString *imageURL;
@property (nonatomic,strong)NSString *shareURL;
//换地址参数
@property (nonatomic,strong)NSString *ptOrderID;
@property (nonatomic,strong)NSString *regionID;
@property (nonatomic,strong)NavigationViewController
*navigationContro;
@end
//
// OCModel.m
// XXX
//
// Created by lirong on 16/4/20.
// Copyright ? 2016年 daming. All rights reserved.
//
#import "OCModel.h"
#import "ShareView.h"
#import "SubjectShopController.h"
@implementation OCModel
#pragma mark -登录
- (void)pushToLoginViewController{
LoginViewController *vc=[[LoginViewControlleralloc]init];
[self.navigationContro pushViewController:vcanimated:YES];
}
- (void)getiOSID{
JSValue *jsFunc2 =
self.jsContext[@"getId"];
PersonalSetting *s =[[PersonalSettingalloc]init];
BOOL session_nil = s.session ==nil || [s.session isEqualToString:@""];
BOOL addrID_nil = s.addr ==nil||[s.addr isEqualToString:@""];
if (addrID_nil ==
YES) {
s.addr = @"undefined";
}
if (session_nil ==
YES){
s.session =
@"undefined";
}
[jsFunc2 callWithArguments:@[@{@"session": s.session,@"addr":s.addr}]];
}
#pragma mark -选地址
- (void)pushToChooseAddressVCWithParams:(NSDictionary *)params{
PersonalSetting *s=[PersonalSettinggetInstance];
s.send = [params
objectForKey:@"send"];
s.singleOrGroup = [params
objectForKey:@"type"];//"TUAN" "SINGLE"
NSString *Order=[params
objectForKey:@"order"];
NSString *tuan=[params
objectForKey:@"tuan"];
s.tuan = tuan;
LocateAndChooseAddressControllerParam *param = [[LocateAndChooseAddressControllerParamalloc]init];
param.from =@"去结算页";
param.pt_order_id = Order;
LocateAndChooseAddressController *vc= [LocateAndChooseAddressControllercreateViewController:param];
__weak
typeof(self)weakSelf=self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.navigationContro
pushViewController:vc animated:YES];
});
// [self.navigationContro pushViewController:vc animated:YES];
}
#pragma mark -分享
- (void)shareTuanMethod{
[TalkingDatatrackEvent:@"呼唤小伙伴"];
PersonalSetting *s=[PersonalSettinggetInstance];
s.paySuccess =
YES;
ShareView *shareV=[ShareViewdefaultShareView];
NSData *data=[NSDatadataWithContentsOfURL:[NSURLURLWithString:self.imageURL]];
UIImage *image=[UIImageimageWithData:data];
[shareV setShareContentWithTitle:self.title content:self.content title2:@"" content2:@"" shareImage:imageshareURL:self.shareURL];
[shareV show];
[shareV setShareViewBlock:^(BOOL isSuccess) {
if (isSuccess) {
DLog(@"分享成功");
}else {
DLog(@"分享失败");
}
}];
s.paySuccess =
NO;
}
#pragma mark -checkout成功去结算页
- (void)pushToCheckOrderVCWithDictonry:(NSDictionary *)params Dictionry2:(NSDictionary *)param2{
DLog(@"param2:%@",param2);
DLog(@"params:%@",params);
PersonalSetting *s=[PersonalSettinggetInstance];
s.send = [param2
objectForKey:@"send"];
s.singleOrGroup = [param2
objectForKey:@"type"];
NSString *Order=[param2
objectForKey:@"order"];
CheckOrderControllerCellInfo*cellInfo=[[CheckOrderControllerCellInfoalloc]init];
NSString *limit=[[params
objectForKey:@"limit"]
stringValue];
//购物车
NSMutableArray *cart=[params
objectForKey:@"cart"];
NSDictionary *cartDict=[[NSDictionaryalloc]init];
NSMutableArray *image=[[NSMutableArrayalloc]init];//商品图片数组
NSString *price=@"";
NSString *status=@"";
NSString *title1=@"";
NSString *tuan_id=@"";
NSString *type=@"";
NSString *shopName=@"";
if (cart.count>0) {
cartDict=[cart objectAtIndex:0];
image=[cartDict objectForKey:@"image"];
price=[cartDict objectForKey:@"price"];
status=[cartDict objectForKey:@"status"];
title1=[cartDict objectForKey:@"title1"];
tuan=[cartDict objectForKey:@"tuan"];
type=[cartDict objectForKey:@"type"];
shopName=[cartDict objectForKey:@"shopName"];
}
NSString *cart_title=[params
objectForKey:@"cart_title"];
// NSString *coupon=[params objectForKey:@"coupon"];
// NSString *coupon_disc=[params objectForKey:@"coupon_disc"];
NSMutableArray *coupon_list=[params
objectForKey:@"coupon_list"];//优惠券
NSString *due=[params
objectForKey:@"due"];
NSString *inventory=[[params
objectForKey:@"inventory"]
stringValue];
NSString *cre = [params
objectForKey:@"cre"];
NSString *useCre = [params
objectForKey:@"use_cre"];
//给结算页传值
cellInfo.title=title1;
cellInfo.credit = credit;
cellInfo.use_credit = useCredit;
cellInfo.groupNumber=[NSStringstringWithFormat:@"%@",type];
cellInfo.price=price;
cellInfo.due=due;
cellInfo.cart_title = cart_title;
cellInfo.couponList = [[NSMutableArrayalloc]init];
for (int i=0;i<coupon_list.count;i++){
SkuCheckoutModel *model=[[CheckoutModelalloc]init];
model.id = coupon_list[i][@"id"];
model.cash = coupon_list[i][@"cash"];
model.valid = coupon_list[i][@"valid"];
model.coupon_status = coupon_list[i][@"coupon_status"];
model.msg1 = coupon_list[i][@"msg1"];
model.msg2 = coupon_list[i][@"msg2"];
model.threshold = coupon_list[i][@"threshold"];
[cellInfo.couponList
addObject:model];//优惠券
}
if (buy_limit !=nil&&![buy isEqualToString:@""]) {
cellInfo.limitNum=[buy intValue];
}
if (inventory !=nil&&![inventoryisEqualToString:@""]) {
cellInfo.stockQuantity=[inventory
intValue];
}
cellInfo.shopName=shopName;
if (_block) {
_block(cellInfo,tuan_id,ptOrderID);
}
}
#pragma mark -回首页
- (void)pushToRootVC{
[self.navigationContropopToRootViewControllerAnimated:YES];
}
#pragma mark -跳转到XXX页面
- (void)pushToFightGroupDetailVCWithPtOrderID:(NSString *)ptOrderID{
DetailViewController *vc=[[DetailViewController alloc]init];
vc.order=ptOrderID;
__weak
typeof(self)weakSelf=self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.navigationContro
pushViewController:vc animated:YES];
});
// [self.navigationContro pushViewController:vc animated:YES];
}
#pragma mark -跳转到店铺页面
- (void)pushToShopVCWithShopID:(NSString *)shopID{
SubjectShopController *vc=[[SubjectShopControlleralloc]init];
vc.supplier_id=shopID;
__weak
typeof(self)weakSelf=self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf.navigationContro
pushViewController:vc animated:YES];
});
// [self.navigationContro pushViewController:vc animated:YES];
}
- (void)showAlert:(NSString *)title msg:(NSString *)msg {
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertView *a = [[UIAlertViewalloc]initWithTitle:titlemessage:msgdelegate:nilcancelButtonTitle:@"Ok"otherButtonTitles:nil,nil];
[a show];
});
}
#pragma mark - 确认收货发红包
- (void)shareRedPaper{
PersonalSetting *s=[PersonalSettinggetInstance];
s.paySuccess =
YES;
ShareView *shareV=[ShareViewdefaultShareView];
// NSData *data=[NSData dataWithContentsOfURL:[NSURL URLWithString:self.imageURL]];
// UIImage *image=[UIImage imageWithData:data];
[shareV setShareContentWithTitle:self.titlecontent:self.contenttitle2:@""content2:@""shareImage:nilshareURL:self.shareURL];
[shareV show];
[shareV setShareViewBlock:^(BOOL isSuccess) {
if (isSuccess) {
DLog(@"分享成功");
}else {
DLog(@"分享失败");
}
}];
s.paySuccess =
NO;
}
@end
(7):webView 多次push页面会造成crash,一般都是crash在webView的线程WebThread里。
这种情况是因为主线程被卡住了。主线程被卡住是非常常见的场景,具体表现就是程序不响应任何的UI交互。所以我们在webView里面进行push操作的时候,一定要在主线程里面进行。
__strongtypeof(weakSelf)strongSelf=weakSelf;
dispatch_async(dispatch_get_main_queue(),
^{
[strongSelf.navigationControllerpushViewController:vcanimated:YES];
});
4:JS端的写法:(举例说明)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var isLogin =
false;
var session_id ;
var addr_id =
"";
var device;
function _isIoS(){
var ua = navigator.userAgent.toLowerCase();
if(ua.match(/iPhone\sOS/i) ==
‘iphone os‘){
return
true;
}else{
return
false;
}
}
function _isAndroid(){
var ua = navigator.userAgent.toLowerCase();
var msgss = navigator.userAgent
if(ua.match(/Android/i) ==
‘android‘){
return
true;
}else{
return
false;
}
}
if(_isAndroid()){
device = ‘android‘;
}else
if(_isIoS()){
device = ‘ios‘;
}
function toLogin(){
if(device==‘android‘){
PtUbossJsInterface.toLogin()
}else
if(device == ‘ios‘){
OCModel.pushToLoginViewController()
}else{
}
}
function getLoginSession_id(){
if(device==‘android‘){
PtUbossJsInterface.getLoginSession_id(setIsLogin);
}else
if(device == ‘ios‘){
OCModel.getSessionID()
}else{
}
}
function toAddrList(){
if(device==‘android‘){
PtUbossJsInterface.toAddrList()
}else
if(device == ‘ios‘){
OCModel.pushToChooseAddressVCWithParams({})
}else{
}
}
function getAddrId(){
if(device==‘android‘){
PtUbossJsInterface.getAddrId(setAddrId)
}else
if(device == ‘ios‘){
OCModel.getAddressID()
}else{
}
}
function shareTuanLink(){
if(device==‘android‘){
PtUbossJsInterface.shareTuanLink(‘c‘,‘d‘,‘d‘,‘dfs‘)
}else
if(device == ‘ios‘){
OCModel.shareTuanMethodShareURLTitleContentImageURL(‘a‘,‘b‘,‘c‘,‘d‘)
}else{
}
}
function toPtCheckout(){
if(device==‘android‘){
PtUbossJsInterface.toPtCheckout()
}else
if(device == ‘ios‘){
OCModel.pushToCheckOrderVCWithDictonry()
}else{
}
}
function toFirstActivity(){
if(device==‘android‘){
PtUbossJsInterface.toFirstActivity()
}else
if(device == ‘ios‘){
OCModel.pushToRootVC()
}else{
}
}
function toShop(){
if(device==‘android‘){
PtUbossJsInterface.toShop()
}else
if(device == ‘ios‘){
OCModel.pushToShopVCWithShopID(‘shop_id‘)
}else{
}
}
function toPtDetail(){
if(device==‘android‘){
PtUbossJsInterface.toPtDetail(‘pt11604221104zmlow3‘);
}else
if(device == ‘ios‘){
OCModel.pushToFightGroupDetailVCWithPtOrderID(‘pt11604221104zmlow3‘);
}else{
}
}
</script>
<p id="id_device">当前的手机机型</p>
<div>
<article
id="id_login">登入状态</article>
<button onclick="toLogin()">到登入页</button>
<button
onclick="getLoginSession_id()">获取登入状态</button>
</div>
<br>
<!--**********************************************************************-->
<div>
<article id="id_addr_id">当前选中的地址id</article>
<button onclick="toAddrList()">跳转到地址选择页面</button>
<button onclick="getAddrId()">获取当前选中地址的id</button>
</div>
<br>
<!--**********************************************************************-->
<div>
<article></article>
<button onclick="shareTuanLink()">分享团链接的方法</button>
</div>
<br>
<!--**********************************************************************-->
<div>
<article></article>
<button onclick="toPtCheckout()">到结算页的方法</button>
</div>
<br>
<!--**********************************************************************-->
<div>
<article></article>
<button
onclick="toFirstActivity()">跳回首页的方法</button>
</div>
<br>
<!--**********************************************************************-->
<div>
<article></article>
<button onclick="toShop()">跳转到店铺的方法</button>
</div>
<br>
<!--**********************************************************************-->
<div>
<article></article>
<button onclick="toPtDetail()">到详情页的方法</button>
</div>
<br>
<!--**********************************************************************-->
<script>
function setIsLogin(s){
session_id = s;
alert(s);
if(session_id){
document.getElementById(‘id_login‘).innerHTML =‘用户已登入‘+session_id;
}else{
document.getElementById(‘id_login‘).innerHTML =‘用户未登入‘
}
}
document.getElementById(‘id_device‘).innerHTML="当前设备是"+device;
if(isLogin){
document.getElementById(‘id_login‘).innerHTML =‘用户已登入‘
}else{
document.getElementById(‘id_login‘).innerHTML =‘用户未登入‘
}
function setAddrId(id){
addr_id = id;
document.getElementById(‘id_addr_id‘).innerHTML = addr_id;
}
</script>
</body>
</html>