文件目录结构
1DA4FDEB-8900-4C4D-BDA9-8B47BDA17D83.png- WKWebViewJavascrtptBridge.swift 负责WKWebView的封装,oc<->js的之间通信
- WKWebViewJavascriptBridgeBase.swift 负责对消息的内部处理以及标识
- WKWebViewJavascriptBridgeJS.swift 复制iOS原生调用js,和js调用iOS原生的一个接口,可以理解成一个js文件,这里存了一个window.WKWebViewJavascriptBridge对象,这个对象就是提供了用来协调js与iOS原生交互的一个重要枢纽
WKWebViewJavascrtptBridge 问价内部解剖
源码如下
import WebKit
@available(iOS 9.0, *)
public class WKWebViewJavascriptBridge: NSObject {
private let nativeHandler = "nativeHandler"
private let iOS_Native_FlushMessageQueue = "iOS_Native_FlushMessageQueue"
private weak var webView: WKWebView?
private var base: WKWebViewJavascriptBridgeBase!
/*初始化
*webView 监听的 webView
*/
public init(webView: WKWebView) {
super.init()
self.webView = webView
base = WKWebViewJavascriptBridgeBase()
base.delegate = self
addScriptMessageHandlers()
}
/*将要释放的时候一处所有注册事件
*/
deinit {
removeScriptMessageHandlers()
}
// MARK: - Public Funcs
public func reset() {
base.reset()
}
/*注册事件
*handlerName 协议名称
*handler 响音的函数
*/
public func register(handlerName: String, handler: @escaping WKWebViewJavascriptBridgeBase.Handler) {
base.messageHandlers[handlerName] = handler
}
/*移除事件
*handlerName 协议名称
*/
public func remove(handlerName: String) -> WKWebViewJavascriptBridgeBase.Handler? {
return base.messageHandlers.removeValue(forKey: handlerName)
}
/*发送事件
*handlerName 协议名称
*data 传递的数据
*callback 回调
*/
public func call(handlerName: String, data: Any? = nil, callback: WKWebViewJavascriptBridgeBase.Callback? = nil) {
base.send(handlerName: handlerName, data: data, callback: callback)
}
/* * iOS 核心代码
* 从WKWebViewJavascriptBridge.fetchQueue事件队列中获取事件
*/
private func flushMessageQueue() {
webView?.evaluateJavaScript("WKWebViewJavascriptBridge._fetchQueue();") { (result, error) in
if error != nil {
print("WKWebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: \(String(describing: error))")
}
//拿到js消息队列中的消息后开始处理
guard let resultStr = result as? String else { return }
self.base.flush(messageQueueString: resultStr)
}
}
/*向js注册 名为 nativeHandler 事件,事件处理对象为 LeakAvoider()
*向js注册 名为 iOS_Native_FlushMessageQueue 事件,事件处理对象为 LeakAvoider()
*/
private func addScriptMessageHandlers() {
webView?.configuration.userContentController.add(LeakAvoider(delegate: self), name: nativeHandler)
webView?.configuration.userContentController.add(LeakAvoider(delegate: self), name: iOS_Native_FlushMessageQueue)
}
/*向js移除 名为 nativeHandler 事件协议
*向js移除 名为 iOS_Native_FlushMessageQueue 事件协议
*/
private func removeScriptMessageHandlers() {
webView?.configuration.userContentController.removeScriptMessageHandler(forName: nativeHandler)
webView?.configuration.userContentController.removeScriptMessageHandler(forName: iOS_Native_FlushMessageQueue)
}
}
/*
*原生调用js的对调
*/
extension WKWebViewJavascriptBridge: WKWebViewJavascriptBridgeBaseDelegate {
//向webView里面注入javascript
func evaluateJavascript(javascript: String) {
webView?.evaluateJavaScript(javascript, completionHandler: nil)
}
}
/*
*LeakAvoider 对象的代理方法
*/
extension WKWebViewJavascriptBridge: WKScriptMessageHandler {
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
/*
* 当js向iOS发送第一个消息的时候表示web页面已经加载到对应的script标签来,这个时候可以WKWebViewJavascriptBridgeJS代码注入
*/
if message.name == nativeHandler {
base.injectJavascriptFile()
}
/*
* js通知iOS端消息队列更新了,快去干活
*/
if message.name == iOS_Native_FlushMessageQueue {
flushMessageQueue()
}
}
}
/* *
* LeakAvoider js调用原生方法的代理类
*/
class LeakAvoider: NSObject {
weak var delegate: WKScriptMessageHandler?
init(delegate: WKScriptMessageHandler) {
super.init()
self.delegate = delegate
}
}
/*
* 收到js发给原生的消息,交给delegate处理消息
*/
extension LeakAvoider: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
delegate?.userContentController(userContentController, didReceive: message)
}
}
在这里面我把每一个方法所做的功能都写上了注释
首先找到初始化方法
/*初始化
*webView 监听的 webView
*/
public init(webView: WKWebView) {
super.init()
self.webView = webView
base = WKWebViewJavascriptBridgeBase()
base.delegate = self
addScriptMessageHandlers()
}
暂且先不看base = WKWebViewJavascriptBridgeBase()做了什么和base.delegate = self
先来看看这个addScriptMessageHandlers()方法做了什么
private func addScriptMessageHandlers() {
webView?.configuration.userContentController.add(LeakAvoider(delegate: self), name: nativeHandler)
webView?.configuration.userContentController.add(LeakAvoider(delegate: self), name: iOS_Native_FlushMessageQueue)
}
- webView?.configuration.userContentController.add(LeakAvoider(delegate: self), name: nativeHandler)监听js调用触发这个协议方法nativeHandler,并发消息的接收者这设置成LeakAvoider对象的代理对象,也就是当前的对象self;
- webView?.configuration.userContentController.add(LeakAvoider(delegate: self), name: iOS_Native_FlushMessageQueue)监听js调用触发协议方法iOS_Native_FlushMessageQueue,同时也把消息的接受者设置成LeakAvoider对象的代理对象,也就是当前的对象self。
继续往下看LeakAvoider的代理都干了什么事
当webview收到js发出的消息第一时间收到这个消息的就是LeakAvoider类的实例对象的userContentController方法
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
delegate?.userContentController(userContentController, didReceive: message)
}
然后LeakAvoider对象把消息传递给了自己的代理对象,也就是上面的初始化LeakAvoider对象时传进来的对象,也就是WKWebviewJavascriptBridge对象,接着又把收到的消息分发了出去
WKWebviewJavascriptBridge的userContentController方法源码:
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
/*
* 当js向iOS发送第一个消息的时候表示web页面已经加载到对应的script标签来,这个时候可以WKWebViewJavascriptBridgeJS代码注入
*/
if message.name == nativeHandler {
base.injectJavascriptFile()
}
/*
* js通知iOS端消息队列更新了,快去干活
*/
if message.name == iOS_Native_FlushMessageQueue {
flushMessageQueue()
}
}
我注释写得很清楚
首先,当收到js触发nativeHandler这个协议时,是需要原生把WKWebViewJavascriptBridgeJS这段js代码注入到js环境中,base.injectJavascriptFile()方法就是把WKWebViewJavascriptBridgeJS.swift文件中的js代码读取处理,并植入js环境中。
其次,当js触发iOS_Native_FlushMessageQueue这个协议时,则表示js环境中的消息队列里有新的事件需要原生处理,然后flushMessageQueue方法就去处理消息队列
/* * iOS 核心代码
* 从WKWebViewJavascriptBridge.fetchQueue事件队列中获取事件
*/
private func flushMessageQueue() {
webView?.evaluateJavaScript("WKWebViewJavascriptBridge._fetchQueue();") { (result, error) in
if error != nil {
print("WKWebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: \(String(describing: error))")
}
//拿到js消息队列中的消息后开始处理
guard let resultStr = result as? String else { return }
self.base.flush(messageQueueString: resultStr)
}
}
通过这个webview通过调用js中的WKWebViewJavascriptBridge._fetchQueue();方法获取到js环境中待原生处理的消息事件,然后把消息的解析过程交给self.base.flus处理消息。
另外就是WKWebViewJavascriptBridge提供外部注册js的监听事件的方法,register函数
/*注册事件
*handlerName 协议名称
*handler 响音的函数
*/
public func register(handlerName: String, handler: @escaping WKWebViewJavascriptBridgeBase.Handler) {
base.messageHandlers[handlerName] = handler
}
iOS原生主动给js环境发送消息,call函数
/*发送事件
*handlerName 协议名称
*data 传递的数据
*callback 回调
*/
public func call(handlerName: String, data: Any? = nil, callback: WKWebViewJavascriptBridgeBase.Callback? = nil) {
base.send(handlerName: handlerName, data: data, callback: callback)
}
WKWebViewJavascriptBridgeJS.swift 文件解剖
其实这个就是原生想js环境植入一个WKWebViewJavascriptBridgeJS函数并执行这样一个操作,具体函数内部做了什么,下面来看源码:
function() {
if (WKWebViewJavascriptBridge) {
return;
}
window.WKWebViewJavascriptBridge = {
/* 外部注册一个方法
* 注册方式:bridge.registerHandler('protocolName', function(data, responseCallback) {})
*/
registerHandler: registerHandler,
//js->iOS会调用的函数
callHandler: callHandler,
//消息事件队列
_fetchQueue: _fetchQueue,
//iOS->js会调用的函数
_handleMessageFromiOS: _handleMessageFromiOS
};
//发送消息队列
var sendMessageQueue = [];
//事件管理者
var messageHandlers = {};
//回调的链表
var responseCallbacks = {};
//消息编号 自增长
var uniqueId = 1;
//外部js注册方法
function registerHandler(handlerName, handler) {
messageHandlers[handlerName] = handler;
}
//外部执行事件的函数
// handlerName 协议名称
// data 传递的数据
// responseCallback 原生回调函数
function callHandler(handlerName, data, responseCallback) {
if (arguments.length == 2 && typeof data == 'function') {
responseCallback = data;
data = null;
}
_doSend({ handlerName:handlerName, data:data }, responseCallback);
}
//js->iOS
//js发送消息给iOS-WKWebView函数
function _doSend(message, responseCallback) {
if (responseCallback) {
//通过callbackID来标识每一条消息的唯一性以及,通过callbackID值为key,responseCallback函数为value进行存放,用于后续回调
var callbackID = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
responseCallbacks[callbackID] = responseCallback;
message['callbackID'] = callbackID;
}
//把消息添加到队列中
sendMessageQueue.push(message);
//核心代码 js发送消息给iOS
//协议名称 iOS_Native_FlushMessageQueue
window.webkit.messageHandlers.iOS_Native_FlushMessageQueue.postMessage(null)
}
//iOS->js
//iOS通过调用此函数取得队列中的事件
function _fetchQueue() {
var messageQueueString = JSON.stringify(sendMessageQueue);
sendMessageQueue = [];
return messageQueueString;
}
//当iOS端callback回调时会走进这个方法
function _dispatchMessageFromiOS(messageJSON) {
var message = JSON.parse(messageJSON);
var messageHandler;
var responseCallback;
//客户端的回调
if (message.responseID) {
responseCallback = responseCallbacks[message.responseID];
if (!responseCallback) {
return;
}
responseCallback(message.responseData);
delete responseCallbacks[message.responseID];
} else {
//客户端主动发送消息给js
if (message.callbackID) {
var callbackResponseId = message.callbackID;
responseCallback = function(responseData) {
_doSend({ handlerName:message.handlerName, responseID:callbackResponseId, responseData:responseData });
};
}
//取得外部消息处理程序
var handler = messageHandlers[message.handlerName];
if (!handler) {
console.log("WKWebViewJavascriptBridge: WARNING: no handler for message from iOS:", message);
} else {
handler(message.data, responseCallback);
}
}
}
//iOS端发送消息给js第一时间收到消息的方法
function _handleMessageFromiOS(messageJSON) {
_dispatchMessageFromiOS(messageJSON);
}
//知识点还不足 需大神补充
setTimeout(_callWVJBCallbacks, 0);
function _callWVJBCallbacks() {
var callbacks = window.WKWVJBCallbacks;
delete window.WKWVJBCallbacks;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i](WKWebViewJavascriptBridge);
}
}
}
同样注释都写好了,这个是建立在你对Javascript语法和Javascript对象有一定的了解的基础之上,如果没有这方面的知识,建议还是先去学习一下
WKWebViewJavascriptBridgeBase.swift 文件解剖
源码如下
protocol WKWebViewJavascriptBridgeBaseDelegate: AnyObject {
func evaluateJavascript(javascript: String)
}
@available(iOS 9.0, *)
public class WKWebViewJavascriptBridgeBase: NSObject {
//定义一个 Callback 闭包类型
public typealias Callback = (_ responseData: Any?) -> Void
//定义一个 Handler 闭包类型
public typealias Handler = (_ parameters: [String: Any]?, _ callback: Callback?) -> Void
//定义一个 Message 数据类型
public typealias Message = [String: Any]
//发送消息代理对象,因为当前对象是不能给js发送消息的,要依赖于WKWebViewJavascriptBridge中的WKWebView才能给js环境发送消息
weak var delegate: WKWebViewJavascriptBridgeBaseDelegate?
//原生环境待发送给js环境的消息队列
var startupMessageQueue = [Message]()
//原生发送消息给js时,用来标识和存储js执行完成后执行的原生需要执行的回调闭包
var responseCallbacks = [String: Callback]()
//原生用来存储监听js触发某一个协议的时的闭包回调
var messageHandlers = [String: Handler]()
//原生主动发送消息给js的消息唯一标识符,自增!!!
var uniqueId = 0
//初始化
func reset() {
startupMessageQueue = [Message]()
responseCallbacks = [String: Callback]()
uniqueId = 0
}
//原生制动发送消息给js,消息通过callbackID来做标识,把回调的闭包存在responseCallbacks对象中,当js执行完功能后回传一个消息中也带着这个callbackID,通过这个callbackID标识取出相对应的外部调用的回调,并执行
func send(handlerName: String, data: Any?, callback: Callback?) {
var message = [String: Any]()
message["handlerName"] = handlerName
if data != nil {
message["data"] = data
}
if callback != nil {
uniqueId += 1
let callbackID = "native_iOS_cb_\(uniqueId)"
responseCallbacks[callbackID] = callback
message["callbackID"] = callbackID
}
queue(message: message)
}
//从js 的 _fetchQueue 中获取到的事件 数据的交换是使用 json 数据格式
func flush(messageQueueString: String) {
//数据解析
guard let messages = deserialize(messageJSON: messageQueueString) else {
log(messageQueueString)
return
}
for message in messages {
log(message)
//当消息中没有 responseID 这个字段的时候,说明是由原生主动发送消息给js的,这个消息是js的回调,把消息发到给外部处理的闭包然后移除这个消息监听
if let responseID = message["responseID"] as? String {
guard let callback = responseCallbacks[responseID] else { continue }
callback(message["responseData"])
responseCallbacks.removeValue(forKey: responseID)
//如果是js 主动发消息给原生,则这个消息ID是由js那边定义,从消息中取出responseID字段
} else {
//这里的callback 是原生执行完成功能之后要回调给js的消息
var callback: Callback?
//取出消息中的callbackID
if let callbackID = message["callbackID"] {
callback = { (_ responseData: Any?) -> Void in
let msg = ["responseID": callbackID, "responseData": responseData ?? NSNull()] as Message
self.queue(message: msg)
}
} else {
callback = { (_ responseData: Any?) -> Void in
// no logic
}
}
//从messageHandlers获取到handlerName所对应的回调并执行,当执行完成之后通过调用callback把结果回传给js环境
guard let handlerName = message["handlerName"] as? String else { return }
guard let handler = messageHandlers[handlerName] else {
log("NoHandlerException, No handler for message from JS: \(message)")
return
}
handler(message["data"] as? [String : Any], callback)
}
}
}
// 向js对象中注入 WKWebViewJavascriptBridgeJS
func injectJavascriptFile() {
let js = WKWebViewJavascriptBridgeJS
delegate?.evaluateJavascript(javascript: js)
}
// 把消息添加到main队列中执行
private func queue(message: Message) {
if startupMessageQueue.isEmpty {
dispatch(message: message)
} else {
startupMessageQueue.append(message)
}
}
// 数据解析
private func dispatch(message: Message) {
guard var messageJSON = serialize(message: message, pretty: false) else { return }
messageJSON = messageJSON.replacingOccurrences(of: "\\", with: "\\\\")
messageJSON = messageJSON.replacingOccurrences(of: "\"", with: "\\\"")
messageJSON = messageJSON.replacingOccurrences(of: "\'", with: "\\\'")
messageJSON = messageJSON.replacingOccurrences(of: "\n", with: "\\n")
messageJSON = messageJSON.replacingOccurrences(of: "\r", with: "\\r")
messageJSON = messageJSON.replacingOccurrences(of: "\u{000C}", with: "\\f")
messageJSON = messageJSON.replacingOccurrences(of: "\u{2028}", with: "\\u2028")
messageJSON = messageJSON.replacingOccurrences(of: "\u{2029}", with: "\\u2029")
let javascriptCommand = "WKWebViewJavascriptBridge._handleMessageFromiOS('\(messageJSON)');"
if Thread.current.isMainThread {
delegate?.evaluateJavascript(javascript: javascriptCommand)
} else {
DispatchQueue.main.async {
self.delegate?.evaluateJavascript(javascript: javascriptCommand)
}
}
}
// 数据序列化
private func serialize(message: Message, pretty: Bool) -> String? {
var result: String?
do {
let data = try JSONSerialization.data(withJSONObject: message, options: pretty ? .prettyPrinted : JSONSerialization.WritingOptions(rawValue: 0))
result = String(data: data, encoding: .utf8)
} catch let error {
log(error)
}
return result
}
// 数据反序列化
private func deserialize(messageJSON: String) -> [Message]? {
var result = [Message]()
guard let data = messageJSON.data(using: .utf8) else { return nil }
do {
result = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! [WKWebViewJavascriptBridgeBase.Message]
} catch let error {
log(error)
}
return result
}
// 打印数据
private func log<T>(_ message: T, file: String = #file, function: String = #function, line: Int = #line) {
#if DEBUG
let fileName = (file as NSString).lastPathComponent
print("\(fileName):\(line) \(function) | \(message)")
#endif
}
}
看这篇文章的前提是你已经会使用WKWebViewJavascriptBridge,而想进一MJExtension JSON数据转模型,MJExtension工程量比较大,估计得一两个星期才能全面看完
网友评论