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

前言

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

目录

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

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

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

样例

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

#import 
#import 

@protocol JSHandlerProtocol 

/*
    多参数的方法
    由于涉及到多参数的问题,从第二个参数开始,外部参数名都要使用大写开头
    因为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

@end

JSHandler.m

#import "JSHandler.h"
#import "LocalSaveManager.h"
#import 

@implementation JSHandler:NSObject

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

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

- (NSString *)getToken
{
   
}

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

使用JSHandler
HomeViewController.m

#import 
#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 

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

@end

NSObject+JSContextTracker.m

#import "NSObject+JSContextTracker.h"
#import 
@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 
#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

你可能感兴趣的:(关于iOS中原生和h5交互的知识总结(一) UIWebView)