关于iOS中原生和h5交互的知识总结(一) UIWebView

前言

第一次写简书,本人是从事iOS开发工作的,由于工作中经常涉及一些原生和h5交互的知识,再加上领导的建议,特来总结一下开发过程中所涉及的知识和坑。

目录

关于iOS中原生和h5交互的知识总结(一)UIWebView
关于iOS中原生和h5交互的知识总结(二)WKWebView

基于UIWebView的实现,请注意以下几点

1.模型注入
2.注入时机*

样例

负责与js交互的工具类
JSHandler.h

#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>

@protocol JSHandlerProtocol <JSExport>

/*
    多参数的方法
    由于涉及到多参数的问题,从第二个参数开始,外部参数名都要使用大写开头
    因为JS调用OC方法时,是将OC方法拼接连成字符串,如果无法区分就会造成无法识别
    比如对于下面的OC方法,JS调用时
    javascript.sayHelloToWithGreeting(参数1,参数2) //正确写法
    javascript.sayHelloTowithGreeting(参数1,参数2) //错误写法(就是注意大小写啦)
*/

//- (void)sayHelloTo:(NSString *)name WithGreeting:(NSString *)greeting;

#pragma mark - methods for js

/**
 js端要传参,调用native端的加密方法
 */
- (NSString *)aesEncryptString:(NSString *)text;

/** 
 js端要传参,调用native端的解密方法
 */
- (NSString *)aesDecryptString:(NSString *)text;


/**
 js端不传参,调用native端获取token
 */
- (NSString *)getToken;


/**
 js端调用客户端,显示登录界面,无返回值
 */
- (void)showLoginScene:(NSString *)message;

@end

@interface JSHandler : NSObject<JSHandlerProtocol>

@end

JSHandler.m

#import "JSHandler.h"
#import "LocalSaveManager.h"
#import <JavaScriptCore/JavaScriptCore.h>

@implementation JSHandler:NSObject

#pragma mark - 实现代理方法
- (NSString *)aesEncryptString:(NSString *)text
{
    
}

- (NSString *)aesDecryptString:(NSString *)text
{
    
}

- (NSString *)getToken
{
   
}

- (void)showLoginScene:(NSString *)message
{
  
}

使用JSHandler
HomeViewController.m

#import <JavaScriptCore/JavaScriptCore.h>
#import "JSHandler.h"
#import "NSObject+JSContextTracker.h"

static NSString *const JSContextObject = @"jsHandler";
@interface HomeViewController ()
@property (weak, nonatomic) IBOutlet UIWebView *webView;
@property (nonatomic, strong) JSContext *jsContext;
@property (nonatomic, strong) JSHandler *jsHandler;

@end

@implementation HomeViewController

/*
可能大多数人都会这样使用,这里为了引出“注入时机”的问题,先演示个常规写法,在viewDidLoad里注入jsHandler对象。
并不是说在viewDidLoad时注入jsHandler不对,而是这样会有注入时机的问题。比如在js加载时我们就去native端获取token,
而不是去点击某个按钮才去获取token。这时JSContext还没有创建完毕,但是我们缺向JSContext中注入jsHandler对象,
所以当在js端使用jsHandler对象时会报找不到jsHandler这个对象的错误!
*/

/*
 有的同学会想如果在webView的这两个代理方法中注入jsHandler对象是否是正确的时机呢
- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webViewDidFinishLoad:(UIWebView *)webView;
答案是否定的,webViewDidStartLoad时JSContext还没有创建,webViewDidFinishLoad看似是页面已经加载完的回调,
但这时JSContext真的有创建完毕吗,肯能有些同学发现在webViewDidFinishLoad时,当你切换html页面的时候,
有时候能找到jsHandler对象,有时不能找到,晕!JSContext对象创建完成,注入jsHandler对象真的是一个很微妙的时机,
并且webView那少的可怜的几个代理方法真的不能解决我们的问题,肿么办,往下看!
*/
- (void)viewDidLoad {
    [super viewDidLoad];
    self.jsHandler = [JSHandler new];
    self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    self.jsContext[JSContextObject] = self.jsHandler;
    self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
        context.exception = exceptionValue;
        NSLog(@"异常信息:%@", exceptionValue);
    };
}

还记得我们引入了#import "NSObject+JSContextTracker.h"这个头文件吗,这是个关键的类目啊,现在给出这个类目的实现
NSObject+JSContextTracker.h


#import <Foundation/Foundation.h>

static NSString *const JSContextTrackerNotifycation = @"JSContextTrackerNotifycation";
@interface NSObject (JSContextTracker)

@end

NSObject+JSContextTracker.m

#import "NSObject+JSContextTracker.h"
#import <JavaScriptCore/JavaScriptCore.h>
@implementation NSObject (JSContextTracker)
/*
这个类目在创建JSContext对象时会发出一个通知,这个类目不需要我们主动去调用,在JSContext对象创建时会自动调用,
至于比较偏底层的原理,网上也有介绍,感觉都是东一块西一块,要不就是英文翻译过来的,看了之后也不是特别理解。
由于我本人理解的也是有些模糊,在此就不解释原理的,怕误导大家,如果之后我弄清楚了,会及时更新的。
感兴趣的朋友也可以网上自行去找找资料,如果弄清楚了可以留言,帮助我和大家解惑,谢谢啦
*/
- (void)webView:(id)unused didCreateJavaScriptContext:(JSContext *)context forFrame:(id)alsoUnused {
    if (!context)
        return;
    [[NSNotificationCenter defaultCenter] postNotificationName:JSContextTrackerNotifycation object:context];
}

@end

现在给出HomeViewController.m最佳注入jsHandler对象的时机

#import <JavaScriptCore/JavaScriptCore.h>
#import "JSHandler.h"
#import "NSObject+JSContextTracker.h"

static NSString *const JSContextObject = @"jsHandler";
@interface HomeViewController ()
@property (weak, nonatomic) IBOutlet UIWebView *webView;
@property (nonatomic, strong) JSContext *jsContext;
@property (nonatomic, strong) JSHandler *jsHandler;

@end

@implementation HomeViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.jsHandler = [JSHandler new];
    NSString *url = @"";
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestReloadRevalidatingCacheData timeoutInterval:60];
    [self.webView loadRequest:request];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(createJSContext:) name:JSContextTrackerNotifycation object:nil];
}

/*
  现在createJSContext中就是注入jsHandler对象的最佳时机
*/
-(void)createJSContext:(NSNotification*)notification
{
    //注意以下代码如果不在主线程调用会发生闪退。
    dispatch_async( dispatch_get_main_queue(), ^{
    
        self.jsContext = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        self.jsContext[JSContextObject] = self.jsHandler;
        
        self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
            context.exception = exceptionValue;
            NSLog(@"异常信息:%@", exceptionValue);
        };
    });
}

最后给出js端的代码,是如何调用原生的,把他作为工具类,专门处理和native交互
native-tool.js

function isAndroid() {
    var u = navigator.userAgent;
    var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1;
    return isAndroid;
}

function isIOS() {
    var u = navigator.userAgent;
    var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
    return isIOS;
}

//得到native端token
function native_getToken() {
    var token;
    if (isIOS()) {
        token = jsHandler.getToken();
    } else if (isAndroid()) {
        token = contact.getToken();
    }

    return token;
}

//显示登录界面
function native_showLoginScene(message) {

    if (isIOS()) {
        jsHandler.showLoginScene(message);
    } else if (isAndroid()) {
        contact.showLoginScene(message);
    }
}

//调用native加密
function native_encrypt(str) {
    var res;
    if (isIOS())
    {
        res = jsHandler.aesEncryptString(str);
    } 
    else if (isAndroid())
    {
        res = contact.encrypt(str);
    }
    return res;
}

UIWebView js和原生交互结束语

这里只介绍了“模型注入”的方式,并且填了“注入时机“这个坑,因为UIWebView暴露给我们的方法太少了,而且iOS11中,UIWebView已经不推荐使用了,所以接下来我们着重介绍iOS的负责web显示的新宠儿WKWebView

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 这篇文章发于2017/03/08 “第七岛”原创公众号 现在在“简书”补充发布 我将与shasha(我的小老板)一...
    Sun渣婷阅读 364评论 0 0
  • 惊闻我友焕莲离世,甚是惋惜。呜呼!为何人生无常,天妒英才? 刚过五十,上要孝亲,下要育儿,她如此抽身离去,怎能忍心...
    喵喵喵喵宝阅读 617评论 0 2
  • 我一闺蜜小A,是个浓眉大眼乐观开朗的女孩,富有灵性。眼角眉稍,笑意流转,仿佛哪怕全世界就只剩下她一个,也能自嗨起来...
    小竹筏儿阅读 518评论 1 2
  • 认识“马云",身边的朋友都说我是奔着他响亮的名字去的,这让我很无语。第一,认识他的时候我并不知道他姓啥叫谁;第二,...
    十月城池阅读 540评论 4 7
  • 热水打在身上,脸上,顺着头发滑落,滴滴答答的水流从发尖摔落地板砖上,溅开了无数的水花,迫不及待落下,无声胜有声...
    简晓爱阅读 218评论 0 0