最近項目中需要使用JS與原生的交互,發(fā)現(xiàn)了不少的問題,做一個總結(jié)
UIWebView
- JS調(diào)用OC無參
- JS調(diào)用OC有一個參數(shù)
- JS調(diào)用OC有多個參數(shù)
- OC調(diào)用JS并傳值
1.創(chuàng)建UIWebView
- (UIWebView *)webView {
if (!_webView) {
_webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
_webView.delegate = self;
NSURL *localHTMLURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"UIWebViewJSInteraction" ofType:@"html"]];
NSURLRequest *request = [NSURLRequest requestWithURL:localHTMLURL];
[_webView loadRequest:request];
}
return _webView;
}
2.通過KVO獲取JS上下文javaScriptContex
@weakify(self)
[[self.webView rac_valuesForKeyPath:@"documentView.webView.mainFrame.javaScriptContext" observer:self] subscribeNext:^(id x) {
@strongify(self)
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
YYWeakProxy *proxy = [YYWeakProxy proxyWithTarget:self];
[context setObject:proxy forKeyedSubscript:@"liqu_app"];
self.context = context;
}];
注: A JSContext object represents a JavaScript execution environment. You create and use JavaScript contexts to evaluate JavaScript scripts from Objective-C or Swift code, to access values defined in or calculated in JavaScript, and to make native objects, methods, or functions accessible to JavaScript.
譯:大致的意思就是,通過這個對象我們可以訪問JavaScript中的變量以及函數(shù)方法
- JS調(diào)用OC方法,是通過將對象注入到
JSContext中,這個對象必須實現(xiàn)JSExport協(xié)議,這個協(xié)議定義了這個對象在JS環(huán)境中能夠調(diào)用的方法
@protocol UIWebViewJSExport <JSExport>
// 注意:此處一定不要寫@optional,否則js不會回調(diào)協(xié)議里面的方法
//@optional
- (void)jsInvokeOCNoParm;
- (void)jsInvokeOCWithParm1:(NSString *)parm1;
- (void)jsInvokeOCWithParm1:(NSString *)parm1 parm2:(NSString *)parm2;
- (NSString *)jsInvokeOCReturnValue;
@end
4 . JS與OC相互交互代碼
// html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS調(diào)用OC方法</title>
</head>
<body>
<input type="button" value="JS調(diào)用OC方法無參數(shù)" onclick="jsInvokeOCNoParm()" disable='true'/>
<input type="button" value="JS調(diào)用OC方法有一個參數(shù)" onclick="jsInvokeOCWithParm1();"/>
<input type="button" value="JS調(diào)用OC方法有兩個參數(shù)" onclick="jsInvokeOCWithParm1Parm2();"/>
<input type="button" value="OC調(diào)用JS方法有返回值" onclick="jsInvokeOCReturnValue();"/>
</body>
<script>
function jsInvokeOCNoParm(){
window.liqu_app.jsInvokeOCNoParm();
}
function jsInvokeOCWithParm1(){
window.liqu_app.jsInvokeOCWithParm1('this is a parm');
}
function jsInvokeOCWithParm1Parm2(){
var model = new Object();
model.name = 'js';
model.age = 18;
window.liqu_app.jsInvokeOCWithParm1Parm2('this is a parm',JSON.stringify(model));
}
function jsInvokeOCReturnValue(){
var str = window.liqu_app.jsInvokeOCReturnValue();
var model = JSON.parse(str);
alert('jsInvokeOCReturnValue___' + model.name);
}
<!--模擬打開App立即獲取App內(nèi)部信息的過程-->
jsInvokeOCReturnValue();
function ocCallJSMethod(str){
var model = JSON.parse(str);
alert('ocCallJSMethod_____' + model.name);
return true;
}
</script>
<style>
input {
display:block;
margin:20px auto;
}
</style>
</html>
- JS調(diào)用OC的函數(shù)
// oc
#pragma mark -UIWebViewJSExport
- (void)jsInvokeOCNoParm {
NSLog(@"%@",NSStringFromSelector(_cmd));
}
- (void)jsInvokeOCWithParm1:(NSString *)parm1 {
NSLog(@"%@,parm:%@",NSStringFromSelector(_cmd),parm1);
}
- (void)jsInvokeOCWithParm1:(NSString *)parm1 parm2:(NSString *)objStr {
//objStr 從html傳出對象到oc,這事一個字符串,需要我們轉(zhuǎn)成oc對象
NSData *data = [objStr dataUsingEncoding:NSUTF8StringEncoding];
id obj = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSLog(@"%@,parm1:%@ parm2:%@",NSStringFromSelector(_cmd),parm1,obj);
}
// 從本地獲取信息到HTML
- (NSString *)jsInvokeOCReturnValue {
UserInfo *info = [UserInfo new];
info.name = @"liqu";
info.age = 18;
// 對象轉(zhuǎn)字符串(實現(xiàn)Android和iOS邏輯統(tǒng)一)
NSString *json = [info mj_JSONString];
return json;
}
- OC調(diào)用JS的函數(shù)
#pragma mark -oc調(diào)用js方法
- (BOOL)ocCallJSMethod {
// 方式1:
UserInfo *info = [UserInfo new];
info.name = @"liqu";
info.age = 18;
JSValue *jsResult = [self.context evaluateScript:[NSString stringWithFormat:@"ocCallJSMethod('%@')",[info mj_JSONString]]];
BOOL result = [jsResult toBool];
NSLog(@"%d",result);
//方式2:
{
JSValue *ocInvokeJs = self.context[@"ocCallJSMethod"];
JSValue *jsResult = [ocInvokeJs callWithArguments:@[[info mj_JSONString]]];
BOOL result = [jsResult toBool];
NSLog(@"%d",result);
}
return result;
}
WKWebView
- 導(dǎo)入
#import <WebKit/WebKit.h> - 向
JS中注入模型對象 - 實現(xiàn)
WKWebView代理,判定引起這個回調(diào)的模型對象,執(zhí)行相應(yīng)代碼
- 創(chuàng)建
WKWebView
- (WKWebView *)webView {
if (!_webView) {
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
configuration.userContentController = [[WKUserContentController alloc]init];
YYWeakProxy <WKScriptMessageHandler>*proxy = (id<WKScriptMessageHandler>)[YYWeakProxy proxyWithTarget:self];
[configuration.userContentController addScriptMessageHandler:proxy name:@"liqu_app"];
_webView = [[WKWebView alloc] initWithFrame:self.view.bounds configuration:configuration];
_webView.UIDelegate = self;
NSURL *localHTMLURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"WKWebViewJSInteraction" ofType:@"html"]];
NSURLRequest *request = [NSURLRequest requestWithURL:localHTMLURL];
[_webView loadRequest:request];
}
return _webView;
}
注意:
[config.userContentController addScriptMessageHandler:'會回調(diào)這個對象里面的方法' name:@"為這個對象打上一個標簽,以便代理中進行區(qū)分"];這個方法中我們需要遵循WKScriptMessageHandler這個協(xié)議,這個協(xié)議只定義了一個方法,這個方法會在JS調(diào)用OC時會被調(diào)用
2.HTML代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS調(diào)用OC方法</title>
</head>
<body>
<input type="button" value="JS調(diào)用OC方法無參數(shù),看日志" onclick="jsInvokeOCNoParm()" disable='true'/>
<input type="button" value="OC調(diào)用JS方法有返回值" onclick="jsInvokeOCReturnValue();"/>
</body>
<script>
function jsInvokeOCNoParm() {
window.webkit.messageHandlers.liqu_app.postMessage('123');
}
function jsInvokeOCReturnValue(){
window.webkit.messageHandlers.liqu_app.postMessage('fetchUserInfo');
}
<!--模擬打開App立即獲取App內(nèi)部信息的過程-->
window.webkit.messageHandlers.liqu_app.postMessage('fetchUserInfo');
function ocCallJSMethod(str){
var model = JSON.parse(str);
alert('ocCallJSMethod_____' + model.name);
return true;
}
</script>
<style>
input {
display:block;
margin:20px auto;
font-size:30px;
}
</style>
</html>
3.WKScriptMessageHandler協(xié)議
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
// 根絕對應(yīng)的name和body執(zhí)行對應(yīng)的邏輯
NSLog(@"%@ --- %@",message.body,message.name);
// deal
if ([message.body isEqualToString:@"fetchUserInfo"]) {
[self ocCallJSMethod];
}else {
}
}
4 .OC調(diào)用JS函數(shù),使用如下方法
- (void)ocCallJSMethod {
UserInfo *info = [UserInfo new];
info.name = @"liqu";
info.age = 18;
NSString *jsMethod = [NSString stringWithFormat:@"ocCallJSMethod('%@')",[info mj_JSONString]];
[_webView evaluateJavaScript:jsMethod
completionHandler:^(id _Nullable obj, NSError * _Nullable
error)
{
NSLog(@"%@",obj);
}];
}
注意:這里我們傳值給JS上下文,都用的
YYWeakProxy對象,這個對象是self的代理,可以這么理解,因為無論UIWebView的
[context setObject:proxy forKeyedSubscript:@"liqu_app"];
或者WKWebView的
[configuration.userContentController addScriptMessageHandler:proxy name:@"liqu_app"];都會對這個proxy進行強引用,會導(dǎo)致循環(huán)引用,雖然使用YYWeakProxy能夠避免循環(huán)引用,但是還是有內(nèi)存泄漏,檢測發(fā)現(xiàn)YYWeakProxy即使在控制器釋放后,這個對象還是沒有調(diào)用dealloc方法,即使使用weak弱引用YYWeakProxy,這個對象也無法釋放,如果你知道怎么解決,希望能告知!
最后附上代碼
項目地址