iOS开发-javaScript交互

前言

脚下交织开发情势迎来了前所未有的向上,跨平台开发、热更新等优点决定了这种格局的严重性地位。尽管前端界面在相互、动效等多地点相差原生应用还有差异,但一定混合开发只会被进一步多的铺面接受。在iOS中,混合开发情势被分为六个时代,分别是iOS7事先的坑爹时代与后来的纯金一代,其分割代表为JavaScriptCore框架

坑爹时代

作为宏观避开iOS7以前版本的幸运儿,我只好从某位前辈的口中获悉那悲惨的时间。作为那多少个年代唯一能与前端界面交互的手段就是UIWebView,先不说它自己的内存泄露缺陷,下面是一段前辈写过的代码:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSString * address = request.URL.absoluteString;
    for (NSString * black in _blackList) {
        if ([address containsString: black]) {
            return NO;
        }
    }
    for (NSString * event in _eventList) {
        if ([address containsString: event]) {
            SEL callback = NSSelectorFromString(_callbacks[event]);
            [self performSelector: callback];
            return [event containsString: @"shouldOpen=1"];
        }
    }
    return YES;
}

在非凡年代,前辈的伙伴们把前端事件的触发条件设置为链接跳转,然后通过链接中的关键字符来判断处理操作。为此,需要定义好些个数据集合来存储这多少个重大字符的拍卖操作。如若赶上应用和前端互换交互数据的时候,那一长串的参数字符全体拼凑在伸手地址里,想想也是醉了。此外的竞相格局就是因而stringByEvaluatingJavaScriptFromString措施来施行js代码。

JavaScriptCore

JavaScriptCore是一套用来对JS代码举行分析和提供执行环境的开源框架,极大的简化了大家的相互过程。下边从连串和JS代码相互调用的两个不等操作介绍其中绝对应的方法

项目调用JS代码

  • JSContext
    一个JSContext对象是JavaScript运转的全局环境目标,它提供了代码运行和注册模式接口的劳务。下面的代码就创办了一个JSContext目的,并且定义了一局部的JS代码出席到实施环境中
    let context = JSContext()
    context.evaluateScript(” var age = 22 “)
    context.evaluateScript(” var name = ‘SindriLin’ “)
    context.evaluateScript(” var birth = 1993-01-01 “)
    context.evaluateScript(” var createPerson =
    function(age, name, birth)
    {
    return {‘age’: age, ‘name’: name, ‘birth’: birth}
    } “)
    context.evaluateScript(” var codeDescription = ‘The code create
    three value and a function to create a dictionary stored person
    information’ “)
    此外,在JS代码执行过程中,可能会合世语法错误等多种破绽百出,通过上面的代码可以对这个不当举办处理
    context?.exceptionHandler = { context, exception in
    print(“Java Script Run Error: (exception)”)
    }

  • JSValue
    JSValue是所有JSContext操作后赶回的值,包装了几乎拥有的数据类型,包括错误和IMP指针等。在JSValue类协会中存在多少个toXXXX取名的办法转换成iOS数据类型以及call艺术来调用方法。下边的代码从JSContext条件中拿走已存在的有的变量,并且实施成立一个存储person信息的字典
    let age = context?.objectForKeyedSubscript(“age”)
    let name = context?.objectForKeyedSubscript(“name”)
    let birth = context?.objectForKeyedSubscript(“birth”)
    let createFunction =
    context?.objectForKeyedSubscript(“createPerson”)
    let codeDescription =
    context?.objectForKeyedSubscript(“codeDescription”)
    let person = createFunction.call(withArguments: [age.toInt32(),
    name.toString(), birth.toString()])

    let personInfo = "name: \(person["name"]) age: \(person["age"] and birth: \(person["birth"])"
    print("The javaScript code description: \(codeDescription.toString())")
    print("The created person \(personInfo) ")
    

经过地点的事例,大家可以看来,只要通晓到JS代码中我们需要调用的主意信息,通过JSContext + JSValue的章程我们就能轻松的在类型中调用前端界面的措施,而不再需要拼接长串参数字符通过链接地址传递给前端界面

JS调用项目代码

JavaScript做客我们代码中的对象以及艺术有二种艺术:BlocksJSExport

  • Blocks
    Java,自定义的block代码能够经过JSContext转换成JS代码中的函数指针调用,这里存在一个坑就是Swift中的闭包不能到位如此的类型转换,因而这种艺术的操作流程在Swift中是这样的:Closure
    -> block ->
    function pointer。在闭包转成block的这一经过中,需要利用一个紧要的重要符@convention
    let stringConvert: @convention(block) (String)->String = {
    let pinyin = NSMutableString(string: $0) as CFMutableString
    CFStringTransform(pinyin, nil, kCFStringTransformToLatin, false)
    CFStringTransform(pinyin, nil,
    kCFStringTransformStripCombiningMarks, false)
    return pinyin as String
    }

    let convertObjc = unsafeBitCast(stringConvert, to: AnyObject.self)
    context?.setObject(convertObjc, forKeyedSubscript: "convertFunc")
    let convertFunc = context?.objectForKeyedSubscript("convertFunc")
    print("林欣达的拼音是\(convertFunc.call(withArguments: ["林欣达"]).toString())")
    

    此刻,只要前端在JS的按钮点击代码中调用convertFunc()这句代码就会进行这些closure中的代码。使用这种方法要注意由于闭包的捕获特性,有可能会导致你的JSContext目的被引述而不能被释放,使用JSContext.current()拿到当前上下文来缓解引用问题

  • JSExport
    JS中调用iOS措施的时候,通过调用JSExport的派生协议情势来实现。所有派生协议的方法会自动提供给JavaScript代码应用,这些在下面的demo中得以观望

实战

在本文demo中自己写了一段JS代码,上面放出这段代码以及运行效果。其中要专注的是按钮的onclik代表按钮点击的响应事件:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
    </head>
    <body>
        <div style="margin-top: 20px">
            <h2 align="center" style="color:#ff0000">JS与iOS交互</h2>
            <input type="button" value="点击后切换控制器的背景颜色" onclick="sindrilin.call()">
        </div>
        <div style="color:#7BBDE5">
            <br />
            <br />
            账户:
            <input id="account" type="text">
            <br />
            密码:
            <input id="password" type="password">
        </div>
        <div>
            <input type="button" value="登录" onclick="login()">
        </div>

        <script>

            var login = function()
            {
                account = document.getElementById("account")
                password = document.getElementById("password")
                var accountInfo = JSON.stringify({"account": account.value, "password": password.value});
                sindrilin.login(accountInfo);
            }

            var alertFromIOS = function(message)
            {
                    alert(message)
            }

        </script>
    </body>
</html>

首先我们需要加载这么些HTML文件,然后拿走代码运行的大局环境目的。基本上在富有的HTML格式文件中,获取环境目标的keyPath都是平等的:

let jsPath = Bundle.main().pathForResource("interaction", ofType: "html")
webView.loadRequest(URLRequest(url: URL(fileURLWithPath: jsPath!)))
interactionContext = webView.value(forKeyPath: "documentView.webView.mainFrame.javaScriptContext") as? JSContext
interactionContext?.exceptionHandler = {
    print("Interaction Error: \($1?.toString())")
}

对照HTML代码,最上边的按钮点击之后会调用一个sindrilin.call()的法子,这么些法子最后要由我们的控制器来进展拍卖。我们可以把这多少个字符串分成类似Target-Action体制的两局部,前者sindrilin表示响应者,后边call()代表响应事件。其中Target的安装形式如下

interactionContext?.setObject(self, forKeyedSubscript: "sindrilin")

响应者已经有了,那么响应事件也要我们实现代码,这里就需要使用JSExport钻探了。所有这连串似Target-Action的风波触发都会经过这么些协议得到格局实现,由此我们需要自定义响应协议以及响应事件。对于有参数的格局大家需要用@objc(name)的法子给艺术起OC式的办法名,才能保证能被正确调用响应:

@objc protocol LXDInteractionExport: JSExport {
    func call()                                    ///响应sindrilin.call()
    @objc(login:) func login(accountInfo: String)  ///响应sindrilin.login(accountInfo)
}

extension ViewController: LXDInteractionExport {
    func call() {
        print("call from html button clicked")
        view.backgroundColor = UIColor(red: CGFloat(arc4random() % 256) / 255, green: CGFloat(arc4random() % 256) / 255, blue: CGFloat(arc4random() % 256) / 255, alpha: 1)
    }

    func login(accountInfo: String) {
        do {
            if let JSON: [String: String] = try JSONSerialization.jsonObject(with: accountInfo.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: String] {
                print("JSON: \(JSON)")
                let alert = interactionContext?.objectForKeyedSubscript("alertFromIOS")
                let message = "The alert from javascript call\naccount: \(JSON["account"]) and password: \(JSON["password"])"
                _ = alert?.call(withArguments: [message])
            }
        } catch {
            print("Error: \(error)")
        }      
    }
}

用户在前端界面输入账户和密码音讯之后点击登录就会调用login(accountInfo: String)方法,将用户名和密码拼凑成JSON字符串传递过来。在响应措施中我分析获取对应字段的用户音信,并且组转成新的字符串调用JS的弹窗函数弹出响应。demo下载

关注iOS开发文集收看更多小说
转载请注解原文作者和地方

相关文章