前言
第一次寫簡書,本人是從事iOS開發(fā)工作的意推,由于工作中經(jīng)常涉及一些原生和h5交互的知識驶兜,再加上領(lǐng)導的建議,特來總結(jié)一下開發(fā)過程中所涉及的知識和坑俊扳。
目錄
關(guān)于iOS中原生和h5交互的知識總結(jié)(一)UIWebView
關(guān)于iOS中原生和h5交互的知識總結(jié)(二)WKWebView
基于UIWebView的實現(xiàn)途蒋,請注意以下幾點
1.模型注入
2.注入時機*
樣例
負責與js交互的工具類
JSHandler.h
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
@protocol JSHandlerProtocol <JSExport>
/*
多參數(shù)的方法
由于涉及到多參數(shù)的問題,從第二個參數(shù)開始馋记,外部參數(shù)名都要使用大寫開頭
因為JS調(diào)用OC方法時号坡,是將OC方法拼接連成字符串,如果無法區(qū)分就會造成無法識別
比如對于下面的OC方法梯醒,JS調(diào)用時
javascript.sayHelloToWithGreeting(參數(shù)1宽堆,參數(shù)2) //正確寫法
javascript.sayHelloTowithGreeting(參數(shù)1,參數(shù)2) //錯誤寫法(就是注意大小寫啦)
*/
//- (void)sayHelloTo:(NSString *)name WithGreeting:(NSString *)greeting;
#pragma mark - methods for js
/**
js端要傳參茸习,調(diào)用native端的加密方法
*/
- (NSString *)aesEncryptString:(NSString *)text;
/**
js端要傳參畜隶,調(diào)用native端的解密方法
*/
- (NSString *)aesDecryptString:(NSString *)text;
/**
js端不傳參,調(diào)用native端獲取token
*/
- (NSString *)getToken;
/**
js端調(diào)用客戶端号胚,顯示登錄界面籽慢,無返回值
*/
- (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 - 實現(xiàn)代理方法
- (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
/*
可能大多數(shù)人都會這樣使用,這里為了引出“注入時機”的問題涕刚,先演示個常規(guī)寫法嗡综,在viewDidLoad里注入jsHandler對象。
并不是說在viewDidLoad時注入jsHandler不對杜漠,而是這樣會有注入時機的問題极景。比如在js加載時我們就去native端獲取token察净,
而不是去點擊某個按鈕才去獲取token。這時JSContext還沒有創(chuàng)建完畢盼樟,但是我們?nèi)毕騄SContext中注入jsHandler對象氢卡,
所以當在js端使用jsHandler對象時會報找不到jsHandler這個對象的錯誤!
*/
/*
有的同學會想如果在webView的這兩個代理方法中注入jsHandler對象是否是正確的時機呢
- (void)webViewDidStartLoad:(UIWebView *)webView;
- (void)webViewDidFinishLoad:(UIWebView *)webView;
答案是否定的晨缴,webViewDidStartLoad時JSContext還沒有創(chuàng)建译秦,webViewDidFinishLoad看似是頁面已經(jīng)加載完的回調(diào),
但這時JSContext真的有創(chuàng)建完畢嗎击碗,肯能有些同學發(fā)現(xiàn)在webViewDidFinishLoad時筑悴,當你切換html頁面的時候,
有時候能找到jsHandler對象稍途,有時不能找到阁吝,暈!JSContext對象創(chuàng)建完成械拍,注入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"這個頭文件嗎,這是個關(guān)鍵的類目啊迄损,現(xiàn)在給出這個類目的實現(xiàn)
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)
/*
這個類目在創(chuàng)建JSContext對象時會發(fā)出一個通知定躏,這個類目不需要我們主動去調(diào)用,在JSContext對象創(chuàng)建時會自動調(diào)用海蔽,
至于比較偏底層的原理共屈,網(wǎng)上也有介紹绑谣,感覺都是東一塊西一塊党窜,要不就是英文翻譯過來的,看了之后也不是特別理解借宵。
由于我本人理解的也是有些模糊幌衣,在此就不解釋原理的,怕誤導大家壤玫,如果之后我弄清楚了豁护,會及時更新的。
感興趣的朋友也可以網(wǎng)上自行去找找資料欲间,如果弄清楚了可以留言楚里,幫助我和大家解惑,謝謝啦
*/
- (void)webView:(id)unused didCreateJavaScriptContext:(JSContext *)context forFrame:(id)alsoUnused {
if (!context)
return;
[[NSNotificationCenter defaultCenter] postNotificationName:JSContextTrackerNotifycation object:context];
}
@end
現(xiàn)在給出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];
}
/*
現(xiàn)在createJSContext中就是注入jsHandler對象的最佳時機
*/
-(void)createJSContext:(NSNotification*)notification
{
//注意以下代碼如果不在主線程調(diào)用會發(fā)生閃退猎贴。
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端的代碼班缎,是如何調(diào)用原生的蝴光,把他作為工具類,專門處理和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);
}
}
//調(diào)用native加密
function native_encrypt(str) {
var res;
if (isIOS())
{
res = jsHandler.aesEncryptString(str);
}
else if (isAndroid())
{
res = contact.encrypt(str);
}
return res;
}
UIWebView js和原生交互結(jié)束語
這里只介紹了“模型注入”的方式达址,并且填了“注入時機“這個坑蔑祟,因為UIWebView暴露給我們的方法太少了,而且iOS11中沉唠,UIWebView已經(jīng)不推薦使用了疆虚,所以接下來我們著重介紹iOS的負責web顯示的新寵兒WKWebView