如何设计通用WebAPI之Swift实现(二)

作者: 微微笑的蜗牛 | 来源:发表于2017-04-02 23:06 被阅读66次

    上篇文章说了设计通用api的思路,下面来具体说下怎么融合到WebView中。这里我只对UIWebView做了接入,WKWebView也是类似。

    bridge.js

    bridge.js是js端调用oc功能函数的集合。

    其中定义个SLWebBridge的全局变量,里面定义了GLOBAL_FUNC_INDEX=0(表示callBackId初始为0),如果每次js调用oc有回调,那么callBackId会递增。

    在webViewDidFinishLoad的时候注入该js。然后在后续就可以利用其进行通信了。

    BaseWebAPI

    BaseWebAPI遵循一个协议WebAPIProtocol。

    protocol WebAPIProtocol {
     // 所属的module名
        func module() -> String
        // 该方法会动态调用,在BaseWebAPI里面实现
        func callNativeMethod(name: String, parameter: [String: AnyObject]?, callback: SLCallback?)
    }
    
    class BaseWebAPI: NSObject, WebAPIProtocol {
        
        func module() -> String {
            return ""
        }
        
        func callNativeMethod(name: String, parameter: [String: AnyObject]?, callback: SLCallback?) {
            let sel = name + ":callback:"
            let seletor = NSSelectorFromString(sel)
            
            guard self.responds(to: seletor) else {
                print("\(self) not responds \(sel)")
                return
            }
            
            let imp = self.method(for: seletor)
            
            if let imp = imp {
                typealias function = @convention(c) (AnyObject, Selector, [String: AnyObject]?, SLCallback?) -> Void
                
                let call = unsafeBitCast(imp, to: function.self)
                
                call(self, seletor, parameter, callback)
            }
            
            if let callback = callback {
                callback(nil)
            }
        }
    }
    

    负责各自模块的webAPI会继承于BaseWebAPI。比如UIWebAPI.

    class UIWebAPI: BaseWebAPI {
        func test(_ params:[String: AnyObject]?, callback: SLCallback?) {
            print("test UIWebAPI");
            if let params = params {
                print("params:\(params)")
            }
            callback?(["a":22])
        }
    }
    

    native端的bridge

    WebBridge主要处理了bridge.js的注入,webAPI的注册,js调用oc设定url的解析等。

    最主要的就是func handleWebEvent(_ url: URL?, webView: UIWebView) -> Bool方法。

    关于各个参数的获取上一篇文章中都讲过,主要说下native端生成callback。
    如果有callbackId存在,则会生成callback。callback会带有个dict的参数,以便native传递参数给js。params会序列化成jsonString。最后会调用invokeWebMethod传入callbackId和params。

    var callback: SLCallback?
            
            //cb,在js端是个id,根据id找到对应的function
            let callbackId = url.objectForKey("cb")
            if let callbackId = callbackId {
                // 生成callback
                callback = { result in
                    guard let result = result else {
                        return
                    }
                    
                    // 将result-->string
                    do {
                        let jsonData = try JSONSerialization.data(withJSONObject: result as Any, options: JSONSerialization.WritingOptions.prettyPrinted)
                        
                        var jsonString = String(data: jsonData, encoding: String.Encoding.utf8)
                        
                        jsonString = jsonString ?? "{}"
                        
                        let script = String(format: "SLWebBridge.invokeWebMethod(%@,%@);", callbackId, jsonString!)
                        
                        print("\(jsonString)")
                        
                        print("\(script)")
                        
                        webView.stringByEvaluatingJavaScript(from: script)
                        
                    } catch {
                        print("json to string error")
                    }
                }
            }
            
            let webApi = webAPI(module)
            if let webApi = webApi {
                webApi.callNativeMethod(name: method, parameter: dict, callback: callback)
            }
    

    自定义webView

    SLWebView会持有WebBridge。init的时候会创建webBridge。

    webBridge = WebBridge(webView: self, webViewDelegate: nil)
    
    

    WebBridge中的init函数,将webView.delegate设成WebBridge。

    init(webView: UIWebView, webViewDelegate: UIWebViewDelegate?) {
            apiDict = [String:WebAPIProtocol]();
    
            super.init()
            
            self.webView = webView
            self.webViewDelegate = webViewDelegate
            webView.delegate = self
            
            // 注入js
            injectBridgeScript()
        }
    

    如果外部需要设置webView.delegate,则需要通过webViewDelegate。在所有UIWebViewDelegate函数的地方都会调用webViewDelegate把回调传出去。

    if let webViewDelegate = webViewDelegate {
           if webViewDelegate.responds(to: #selector(webViewDidStartLoad(_:))) {
               webViewDelegate.webViewDidStartLoad!(webView)
           }
       }
    
    class SLWebView: UIWebView {
        var webBridge: WebBridge?
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            
            commonInit()
        }
        
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            
            commonInit()
        }
        
        deinit {
            webBridge = nil
            self.delegate = nil
        }
        
        private func commonInit() {
            webBridge = WebBridge(webView: self, webViewDelegate: nil)
        }
        
        //MARK: Register API
        func registerWebAPI(_ module: String, _ api: WebAPIProtocol) {
            webBridge?.registerWebAPI(module, api)
        }
        
        func unregisterWebAPI(_ module: String) {
            webBridge?.unregisterWebAPI(module)
        }
    
    

    测试Demo

    <!DOCTYPE html>
    <html>
    <head>
     <title>test</title>
    </head>
    <body>
        <p>测试</p>
     <button onclick="test()">click</button>
     // 引用bridge.js
     <script src="bridge.js"></script>
     <script>
      function test() {
                SLWebBridge.invokeClientMethod("ui","test",{a:5}, function(result) {
                alert(result.a);});
      }
     </script>
    </body>
    </html>
    

    点击button,会触发js调用native,ui模块的test方法,并传入参数。在调用之后,会有回调,alert native传过来的结果。

    在ViewController中载入webView。

    override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
            let uiApi = UIWebAPI()
            
            // 注册webAPI
            webView.registerWebAPI("ui", uiApi)
            
            let path = Bundle.main.path(forResource: "test", ofType: "html")
    
            guard let p = path, p.characters.count > 0 else {
                return
            }
            
            do {
               let content = try String(contentsOfFile: p)
                webView.loadHTMLString(content, baseURL: Bundle.main.bundleURL)
            } catch {
                print("error")
            }
        }
    

    github地址:SLWebBridge

    相关文章

      网友评论

        本文标题:如何设计通用WebAPI之Swift实现(二)

        本文链接:https://www.haomeiwen.com/subject/ckknottx.html