HTML5相比原生APP,有快速更新、跨平台兼容性、开发门槛较低等优势,特别是“快速更新、实时起效”这方面,我们在平时开发过程中,感受尤其深刻,可以这么说目前市场上绝大部分APP在原生的基础上嵌套了H5。那么摆在开发人员面前的要解决的就是怎样很好地实现OC跟JS的交互,如我们在开发的过程中就有以下需求:
- H5页面调用原生的方法打开相册,把选中的照片上传后台服务器,并且回调给H5上传成功后服务器返回的路径path。
- APP未登录状态下,用户在商城(H5)购买商品或者查询订单、积分等时,需要call起APP的登录页面登录并且回调登录成功后的用户标示。
- H5调用APP的SDK支付
- … …
下面我将介绍自己在开发过程中使用的两种方法
一、 stringByEvaluatingJavaScriptFromString,最简单的方法
- stringByEvaluatingJavaScriptFromString只能在主线程执行,比如你调用以下代码,程序就会崩溃:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[webView stringByEvaluatingJavaScriptFromString:@"aaa"];
});
崩溃产生的原因是你在主线程以外的线程调用了UIKit,系统在执行stringByEvaluatingJavaScriptFromString的时候调用了UIKit里的一些方法,所以不允许在主线程之外的线程去调用这个方法。
- 简单调用系统提供的javascript方法
对于一些简单的javascript系统方法,我们可以通过 stringByEvaluatingJavaScriptFromString 做一下简单的调用,并取得返回值。
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSString *title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
NSLog(@"%@", title);
}
下面是将用户的contentId传给H5
[_webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"setAccountIdAndContentId('%@','%@');",[ProjectUtil removeNull:[[NSUserDefaults standardUserDefaults] objectForKey:@"WTuserName"]],self.contentId]];
- 在加载的html里插入代码,并执行
比如我用下面的代码插入了一个名叫alertTest的函数到javascript里实现在页面中显示alert的功能。
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[webView stringByEvaluatingJavaScriptFromString:
@"var script = document.createElement('script');"
"script.type = 'text/javascript';"
"script.text = \"function alertTest(str) { "
"alert(str)"
"}\";"
"document.getElementsByTagName('head')[0].appendChild(script);"];
[webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"alertTest('%@');", @"test"]];
}
注意:值得注意的一点是 - (void)webViewDidFinishLoad:(UIWebView *)webView 方法指的并不是webview完全加载完毕,而是指网页中一个iframe或frame加载完毕,也就是说假如网页里有多个frame,那么 webViewDidFinishLoad 会执行多次。这就会导致上面插入代码的方法会执行多次, stringByEvaluatingJavaScriptFromString 执行JS是一笔不小的时间开销,所以我们应该尽量减少使用它去执行复杂的JS代码。我使用了下面的方式检查是否已经插入并执行了这个函数,typeof alertTest检查了alertTest这个函数是否存在,若存在则不执行if里面的代码插入和代码执行语句。
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
BOOL isExist = [[webView stringByEvaluatingJavaScriptFromString:@"typeof alertTest == \'function\';"] isEqualToString:@"true"];
if (!isExist) {
[webView stringByEvaluatingJavaScriptFromString:
@"var script = document.createElement('script');"
"script.type = 'text/javascript';"
"script.text = \"function alertTest(str) { "
"alert(str)"
"}\";"
"document.getElementsByTagName('head')[0].appendChild(script);"];
[webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"alertTest('%@');", @"test"]];
}
}
二、JavaScriptCore
对于JavaScriptCore的详细讲解这里就不多说了可以点击看这里JavaScriptCore使用,下面主要是我的代码实现
**这里有一点一定要注意, 使用JavaScriptCore要注意内存管理,否则会导致当前controller无法释放:
- (void) webViewDidFinishLoad:(UIWebView *)webView{
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
/**注意:应该注入模型,否则会造成循环引用*/
OnlineObject * onObj = [[OnlineObject alloc] init];
self.jsContext[@"app"] = onObj;
KWeakSelf
self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
KStrongSelf
context.exception = exceptionValue;
NSLog(@"异常信息:%@", exceptionValue);
self.isClose = YES;
};
/**隐藏分享按钮*/
onObj.hiddeBlock = ^(){
KStrongSelf
dispatch_async(dispatch_get_main_queue(), ^{
self.btn_share.hidden = YES;
});
};
/**退出页面*/
onObj.closeShopBlock = ^(){
KStrongSelf
[self.navigationController popViewControllerAnimated:YES];
};
/**显示分享按钮*/
onObj.showShareBlock = ^(){
KStrongSelf
dispatch_async(dispatch_get_main_queue(), ^{
self.btn_share.hidden = NO;
});
};
/**选择照片*/
onObj.chooseImageBlock = ^(NSString * para){
KStrongSelf
[self chooseImage:para];
};
/**删除照片*/
onObj.deleteImageBlock = ^(NSString * para){
KStrongSelf
[self deleteImage:para];
};
/**上传图片*/
onObj.uploadImageBlock = ^(NSString * para){
KStrongSelf
[self uploadImage:para];
};
/**微信支付*/
onObj.chooseWXPayBlock = ^(NSString * para){
KStrongSelf
[self chooseWXPay:para];
};
/**友盟分享*/
onObj.onMenuShareBlock = ^(NSString * para){
KStrongSelf
[self onMenuShare:para];
};
/**显示标题*/
onObj.setTitleBlock = ^(NSString * para){
KStrongSelf
dispatch_async(dispatch_get_main_queue(), ^{
self.label_title.text = para;
});
};
/**设置经纬度*/
onObj.setAppLocationBlock = ^(){
KStrongSelf
[self stepLocation];
};
/**调爆料页面*/
onObj.gotoWmcxBlock = ^(){
KStrongSelf
[self gotoWmcx];
};
/**分享*/
onObj.openYqDialogueBlock = ^(){
KStrongSelf
[self shareClickEvent];
};
/**打开登录页面*/
onObj.openLoginAppBlock = ^(){
KStrongSelf
[self openLogin];
};
}
OnlineObject代码:
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
@protocol JSObjcDelegate <JSExport>
- (void)hideShareMenuItem;
- (void)showShareMenuItem;
- (void)closeShop;
- (void)onMenuShare:(NSString *)parameter;
- (void)chooseImage:(NSString *)parameter;
- (void)uploadImage:(NSString *)parameter;
- (void)chooseWXPay:(NSString *)parameter;
- (void)deleteImage:(NSString *)parameter;
-(void)gotoWmcx;
-(void)setTitle:(NSString *)titleStr;
-(void)setAppLocation;
-(void)openYqDialogue;
-(void)openLoginApp;
@end
@interface OnlineObject : NSObject<JSObjcDelegate>
@property(nonatomic,copy)void(^hiddeBlock)();
@property(nonatomic,copy)void(^showShareBlock)();
@property(nonatomic,copy)void(^closeShopBlock)();
@property(nonatomic,copy)void(^chooseImageBlock)(NSString * para);
@property(nonatomic,copy)void(^deleteImageBlock)(NSString * para);
@property(nonatomic,copy)void(^uploadImageBlock)(NSString * para);
@property(nonatomic,copy)void(^chooseWXPayBlock)(NSString * para);
@property(nonatomic,copy)void(^onMenuShareBlock)(NSString * para);
@property(nonatomic,copy)void(^setTitleBlock)(NSString * para);
@property(nonatomic,copy)void(^setAppLocationBlock)();
@property(nonatomic,copy)void(^gotoWmcxBlock)();
@property(nonatomic,copy)void(^openYqDialogueBlock)();
@property(nonatomic,copy)void(^openLoginAppBlock)();
@end
#import "OnlineObject.h"
@implementation OnlineObject
- (void)hideShareMenuItem{
if (self.hiddeBlock) {
self.hiddeBlock();
}
}
- (void)chooseImage:(NSString *)parameter{
if (self.chooseImageBlock) {
self.chooseImageBlock(parameter);
}
}
-(void)closeShop{
if (self.closeShopBlock) {
self.closeShopBlock();
}
}
- (void)deleteImage:(NSString *)parameter{
if (self.deleteImageBlock) {
self.deleteImageBlock(parameter);
}
}
- (void)uploadImage:(NSString *)parameter{
if (self.uploadImageBlock) {
self.uploadImageBlock(parameter);
}
}
- (void)chooseWXPay:(NSString *)parameter{
if (self.chooseWXPayBlock) {
self.chooseWXPayBlock(parameter);
}
}
- (void)onMenuShare:(NSString *)parameter{
if (self.onMenuShareBlock) {
self.onMenuShareBlock(parameter);
}
}
- (void)showShareMenuItem{
if (self.showShareBlock) {
self.showShareBlock();
}
}
-(void)setTitle:(NSString *)titleStr{
if (self.setTitleBlock) {
self.setTitleBlock(titleStr);
}
}
-(void)setAppLocation{
if (self.setAppLocationBlock) {
self.setAppLocationBlock();
}
}
-(void)gotoWmcx{
if (self.gotoWmcxBlock) {
self.gotoWmcxBlock();
}
}
-(void)openYqDialogue{
if (self.openYqDialogueBlock) {
self.openYqDialogueBlock();
}
}
-(void)openLoginApp{
if (self.openLoginAppBlock) {
self.openLoginAppBlock();
}
}
@end