一、SignalR介紹
SignalR是微軟基於.Net提供的一個開源實時Web RPC庫,可以用在web實時通信的需求上面,比如聊天,web數據更新
SignalR的接口使用十分簡單
由於最近的一個需求需要調研RPC庫,偶然看到github上面一份在iOS上面使用SignalR 客戶端請求的代碼
下面對代碼有意思的地方進行一下分析
二、專門去搜了一下,github上面存在一份400+讚的SignalR native代碼,應該可以使用;
除此之外,工程代碼:https://github.com/adamhartford/SwiftR 提供了另外一種方式
signalR在web上面應用比較成熟,因此jQuery上面有對應的lib,
這一份工程也是使用一個webview,運行本地的html文件,使用jQuery的signalR庫,然後注入一段js,這段js跟本地app橋接通信
html文件:
<html> <head></head> <body> " <script src="\'file:///private/var/mobile/Containers/Data/Application/C5176D20-0015-4DFB-9156-D53DB6544F3E/tmp/SwiftR/jquery-2.1.3.min.js\'"></script> <script src="\'file:///private/var/mobile/Containers/Data/Application/C5176D20-0015-4DFB-9156-D53DB6544F3E/tmp/SwiftR/jquery.signalr-2.2.0.min\'"></script> <script src="\'file:///private/var/mobile/Containers/Data/Application/C5176D20-0015-4DFB-9156-D53DB6544F3E/tmp/SwiftR/SwiftR.js\'"></script>" </body> </html>
可以看到引用了三個js文件,其中上面的庫是本地打包的;根據版本進行選擇
打包的js中 SwiftR.js ,調用signalR庫,同時和本地通信
window.swiftR = { connection: null, hubs: {}, transport: 'auto', headers: {}, messages: {} }; $(function() { $.ajaxSetup({ beforeSend: function (jqxhr) { for (var h in swiftR.headers) { jqxhr.setRequestHeader(h, swiftR.headers[h]); } } }); postMessage({ message: 'ready' }); }); function initialize(baseUrl, isHub) { swiftR.connection = isHub ? $.hubConnection(baseUrl) : $.connection(baseUrl); var connection = swiftR.connection; connection.logging = true; if (!isHub) { connection.received(function(data) { postMessage({ data: data }); }); } connection.starting(function() { postMessage({ message: 'starting' }); }); connection.connectionSlow(function() { postMessage({ message: 'connectionSlow' }); }); connection.reconnecting(function() { postMessage({ message: 'reconnecting' }); }); connection.reconnected(function() { postMessage({ message: 'reconnected' }); }); connection.disconnected(function () { postMessage({ message: 'disconnected' }); }); connection.error(function(error) { postMessage({ message: 'error', error: processError(error) }); }); } function start() { swiftR.connection.start({ transport: swiftR.transport }).done(function() { postMessage({ message: 'connected', connectionId: swiftR.connection.id }); }).fail(function() { postMessage({ message: 'connectionFailed' }); }); } function addHandler(id, hubName, method) { var hub = ensureHub(hubName); hub.on(method, function() { postMessage({ id: id, hub: hub.hubName, method: method, arguments: [].slice.call(arguments) }); }); } function postMessage(msg) { var id = Math.random().toString(36).slice(2, 10); swiftR.messages[id] = msg; if (window.webkit) { webkit.messageHandlers.interOp.postMessage(id); } else { var frame = $('<iframe/>', { src: 'swiftr://' + id }); $('body').append(frame); frame.remove(); } } function ensureHub(name) { var hub = swiftR.hubs[name]; if (!hub) { hub = swiftR.connection.createHubProxy(name); swiftR.hubs[name] = hub; } return hub; } function processError(error) { var err = { message: error.message || 'An unknown error has occurred.' } if (typeof error.source === 'string') { err.source = error.source; } return err; } function readMessage(id) { var msg = swiftR.messages[id]; delete swiftR.messages[id]; return window.webkit ? msg : JSON.stringify(msg); }
其中下面的函數,可以通過webview的接口直接調用。
function start() { swiftR.connection.start({ transport: swiftR.transport }).done(function() { postMessage({ message: 'connected', connectionId: swiftR.connection.id }); }).fail(function() { postMessage({ message: 'connectionFailed' }); }); } function addHandler(id, hubName, method) { var hub = ensureHub(hubName); hub.on(method, function() { postMessage({ id: id, hub: hub.hubName, method: method, arguments: [].slice.call(arguments) }); }); }
當有了回調信息之後,js可以通過下面的方法直接返回
function postMessage(msg) { var id = Math.random().toString(36).slice(2, 10); swiftR.messages[id] = msg; if (window.webkit) { webkit.messageHandlers.interOp.postMessage(id); } else { var frame = $('<iframe/>', { src: 'swiftr://' + id }); $('body').append(frame); frame.remove(); } }
在原生代碼這邊,通過下面的方法主動調用js
func runJavaScript(_ script: String, callback: ((Any?) -> ())? = nil) { guard wkWebView != nil || webView != nil else { jsQueue.append((script, callback)) return } if useWKWebView { wkWebView.evaluateJavaScript(script, completionHandler: { (result, _) in callback?(result) }) } else { let result = webView.stringByEvaluatingJavaScript(from: script) callback?(result as AnyObject!) } }
js的消息通過下面的代碼回調 WKScriptMessageHandler 機制
open func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { if let id = message.body as? String { wkWebView.evaluateJavaScript("readMessage('\(id)')", completionHandler: { [weak self] (msg, err) in if let m = msg as? [String: Any] { self?.processMessage(m) } else if let e = err { print("SwiftR unable to process message \(id): \(e)") } else { print("SwiftR unable to process message \(id)") } }) } }
三、總結
實際使用驗證了一下,看起來還比較穩定
這個工程給我的思路是,可以借鑑web上的解決方案爲app服務,不需要侷限在iOS開發的範圍上。
四、附錄
關於原生和js通信的方式:
https://www.jianshu.com/p/433e59c5a9eb