iOS使用SignalR客戶端代碼典範-橋接web SignalR 客戶端庫

一、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

    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章