参与工做时间比较长了,随着Web前端行业的发展(你们都懂得..),客户端与Web端的交互也愈来愈频繁。其实本人不太喜欢依赖第三方,那种看不到摸不着的东西用起来总感受不是很安心,同时也是为了保证双方都可以高效完成交互的途中不出现一些意料不到的异常,对此,研究了一下JavaScriptCore
这个库仍是颇有必要的,并分别结合UIWebView
以及WKWebView
作了一下交互总结。javascript
写的比较多,若是是第一次接触这个库,建议仍是看一看;若是时间比较紧,想直接知道结果的,送你一个捷径😀传送门,有帮助能够Star一下,十分感谢前端
<input/>
输入一个字符串,经过点击按钮设置成导航标题"以将<#字符串#>"设置成导航Title
,并在网页最底下的label显示出来。分别使用UIWebView
以及WKWebView
实现效果以下:
java
类库里面有12个类(还有两个是负责导入相关类的头文件以及一个关于WebKit的宏定义);在基本的交互过程当中,其实最常使用的有三个:JSContext、JSValue、JSExport
ios
简单的理解为执行JavaScript的一个环境,就好像咱们在绘制View时候须要获取的CGContext
同样,JS的执行须要在此环境之下。git
能够理解成 一种供iOS数据结构与JS数据结构相互转换的包装,也能够当作一种桥接关系,咱们执行JS获取的结果就是经过JSValue对象进行包装传给客户端进行处理的,类型转换官方文档描述以下:github
Objective-C type | JavaScript type
--------------------+---------------------
nil | undefined
NSNull | null
NSString | string
NSNumber | number, boolean
NSDictionary | Object object
NSArray | Array object
NSDate | Date object
NSBlock (1) | Function object (1)
id (2) | Wrapper object (2)
Class (3) | Constructor object (3)复制代码
JavaScriptType
返回的JSValue
数据可经过JSValue.toXXX()
转成客户端相应的数据结构;反之,客户端对象也能够经过JSValue()
的构造方法将相应的数据结构封装成JSValue。web
这是一个协议,官方文档没有暴露出任何的open协议方法,能够理解为一个空协议。bash
一般用法是自定义一个CustomExport : JSExport
,里面将JS能够调用的属性或者方法进行暴露,JS就能够直接使用暴露的属性与方法了。微信
ObjC方法定义样式是很是特殊的,但官方文档给出了转换后JS调用的样式:数据结构
//Objective-C
- (void)doFoo:(id)foo withBar:(id)bar;
//JS
doFooWithBar(foo,bar)复制代码
但这样会有一个缺点,万一,方法有不少个参,拼接起来的JS方法名简直就是日了X;不过这点Apple已经帮咱们想到了,使用JSExportAs
宏,能够将方法名简化,就像Swift
中的typealias
以及ObjC
中的typedef
。
//这样在JS中直接调用doFoo(foo,bar)便可
JSExportAs(doFoo,
- (void)doFoo:(id)foo withBar:(id)bar
);复制代码
以上三个文件就算理解完了,下面来一段小应用😀。
let context = JSContext()
//方法函数定义采用的是ES6语法,由于最近正在学习RN,习惯这么写了呢😀
let _ = context?.evaluateScript("var textnumber = 1")
let _ = context?.evaluateScript("var names = ['Yue','Xiao','Wen']")
let _ = context?.evaluateScript("var triple = (value) => value + 3")
let returnValue = context?.evaluateScript("triple(3)") //由于有返回值,须要接收一下
//打印结果:returnValue = Optional(6)
print("__testValueInContext --- returnValue = \(returnValue?.toNumber())")复制代码
//经过变量名字获取对象
let names = context?.objectForKeyedSubscript("names")
//经过定义顺序的下标获取对象,就是取['Yue','Xiao','Wen']的第0个元素
let firstName = names?.objectAtIndexedSubscript(0) //Yue
//打印结果:names = Optional([Yue, Xiao, Wen]) firstName = Optional(Yue)
print("__testValueInContext --- names = \(names?.toArray())\nfirstName = \(firstName)")
/// 得到context建立的函数变量
let function = context?.objectForKeyedSubscript("triple") //运行 let result = function?.call(withArguments: [3]) //打印结果:context-function's result = Optional(6) print("__testValueInContext --- context-function's result = \(result?.toNumber())")复制代码
/// 捕获JS运行错误
context?.exceptionHandler = {(context,exception) in
print("__testValueInContext --- JS error = \(exception)\n")//打印错误
}
/** 执行一个错误的js,由于没有函数Triple(上面的方法名第一字母是小写的),会调用上面的exceptionHandler 打印结果: JS error = Optional(ReferenceError: Can't find variable: Triple) */
let _ = context?.evaluateScript("Triple(3)")复制代码
仔细看看JSValue
的类型转换,就能够知道,JS中方法就是客户端中的闭包,不过这里楼主采用了Swift和ObjC混编模式,至于缘由下面会说一下:
//得到处理完毕的数据
let result = RITLJSCoreObject.textJavaScriptUseiOS(inObjC: "Hello")
//结果 I am Objc, result = Optional("Hello I am append String")
print("I am Objc, result = \(result?.toString())\n")复制代码
实现方法:
+(JSValue *)textJavaScriptUseiOSInObjC:(NSString *)value
{
JSContext * context = [JSContext new];
//设置block
context[@"stringHandler"] = ^(NSString * oldValue){
NSMutableString * valueHandler = [[NSMutableString alloc]initWithString:oldValue];
[valueHandler appendString:@" I am append String"];
return valueHandler;
};
NSString * js = [NSString stringWithFormat:@"stringHandler('%@')",value];
//注入
return [context evaluateScript:js];
}复制代码
Swift
版本以下,功能实如今本人看来应该是同样的,但在进行注入的时候出现了问题,致使执行方法出现了undefined
。
多是`Swift`的一个bug,也多是我使用不当
若是是我使用错了,还请知道缘由的小伙伴私信一下,十分感谢。复制代码
let context = JSContext()
//初始化一个闭包
let stringHandler : (String) -> String = { (value) in
var value = value
value.append(" I am appending word with closure!")
return value
}
//封装成JSValue
let handerValue = JSValue(object: stringHandler, in: context)
//问题语句$$$$,我怀疑是注入失败..见鬼了
context?.setObject(handerValue, forKeyedSubscript: "stringHandler" as NSString)
let result = context?.evaluateScript("stringHandler('Hello')")
// 结果:I am Swift ,result = Optional("undefined") - - 很无解有没有!!!!
print("I am Swift ,result = \(result?.toString())\n")复制代码
终于能够运用上面的一些方法来实现功能啦。
// 默认为WKWebView
var ritl_tyle = "WKWebView";
// 肯定是webView仍是WKWebView
function sureType(value){
ritl_tyle = value;
};
// 按钮点击
function buttonDidTap (){
var inputValue = $('#input').val()
if (ritl_tyle == "UIWebView"){//若是是UIWebView
RITLExportObject.say(inputValue)//经过注入的对象进行通知客户端
}
else if (ritl_tyle == "WKWebView"){//若是是WKWebView
alert("WKWebView");
window.webkit.messageHandlers.ChangedMessage.postMessage(inputValue);
}
};
function iosTellSomething(value){
//document.getElementById("label").value = "收到啦";//设置给label
$('#label').text(value);
}复制代码
定义一个自定义的协议RITLJSExport
,这里仍然采用混编模式,由于我仍是Swfit
注入失败了...
@protocol RITLJSExport <NSObject,JSExport>
// 相似typedef 将saySomething定义为say,便于JS调用
JSExportAs(say,
- (void)saySomething:(NSString *)thing
);
@end
@interface RITLExportObject : NSObject
/// 进行的回调
@property (nonatomic, copy) void(^dosomething)(NSString *);
/// 将本身注册到JSContext
- (void)registerSelfToContext:(JSContext *)context;
@end
@interface RITLExportObject (RITLJSExport)<RITLJSExport>
@end复制代码
在UIWebViewDelegate
中的webViewDidFinishLoad()
方法中对JSContext
进行截取,并执行操做:
// MARK: UIWebView-Delegate 系列
extension RITLJSWebViewController : UIWebViewDelegate {
func webViewDidFinishLoad(_ webView: UIWebView) {
//得到JSContent对象
guard let context : JSContext = webView.value(forKeyPath: "documentView.webView.mainFrame.javaScriptContext") as! JSContext? else {
return
}
//告诉web,这里是UIWebView
webView.stringByEvaluatingJavaScript(from: "sureType('UIWebView')")
/* 使用的ObjC的Export对象 */
let exportObject = RITLExportObject()
exportObject.dosomething = { [weak self](value) in
guard let value = value else { return }
self?.navigationItem.title = value //设置导航栏
//执行js告知,修改导航栏完毕
webView.stringByEvaluatingJavaScript(from: "iosTellSomething('已将\(value)设置成导航Title')")//回应
}
//进行注入
exportObject.registerSelf(to: context)
}
}
复制代码
首先有一点,WKWebView
是获取不到JSContext
的,那咋办?不要紧,WKWebView
提供给了咱们很是便利的交互,不详细说了,以前写的一篇博文已经介绍了,有兴趣能够看看iOS开发-------基于WKWebView的原生与JavaScript数据交互
添加JavaScript
交互
// 使用WkWebView
lazy var wkWebView : WKWebView = {
let webView: WKWebView = WKWebView(frame: self.view.bounds)
webView.navigationDelegate = self
webView.uiDelegate = self
webView.configuration.userContentController.add(RITLSciptMessageHandler(self), name: "ChangedMessage")// 添加处理
return webView
}()复制代码
在WKNavigationDelegate中告知web当前使用webView的类型:
// 是为了使用JS确认一下类型,实际开发不须要在这个代理下进行以下操做
extension RITLJSWebViewController : WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!){
//确认类型
webView.evaluateJavaScript("sureType('WKWebView')", completionHandler: nil)
}
}复制代码
履行WKScriptMessageHandler
协议,完成交互操做便可
// MARK: WKWebView-Delegate 系列
extension RITLJSWebViewController : WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)
{
//若是body体是约定好的字符串,而且经过标志ChangedMessage传递而且存在body体
guard message.body is String,message.name == "ChangedMessage",let body:String = message.body as? String else { return }
navigationItem.title = body//设置导航
//执行通知HTML
wkWebView.evaluateJavaScript("iosTellSomething('已将\(body)设置成导航Title')") { (_, error) in
print("error = \(error?.localizedDescription)")
}
}
}复制代码
最后记得移除哦
deinit {
print("\(type(of: self)) deinit")
if ritl_useWkWebView {
wkWebView.configuration.userContentController.removeAllUserScripts()
}
}复制代码
这样子,基于JavaScriptCore的UIWebView以及WKWebView交互就算圆满完成啦,欢迎前去Start
此文章来源于第三方转载!!
微信号13142121176