美文网首页iOS-swift
【译】JavaScriptCore Tutorial for i

【译】JavaScriptCore Tutorial for i

作者: xiao333ma | 来源:发表于2016-08-27 22:45 被阅读100次

本片文章翻译自raywenderlich

虽然看起来 SwiftJavaScript 看起来有很大的不同,但你可以使用二者创建一个灵活的 iOSAPP
在这篇 JavaScriptCore 教程中,你将建立一个网页配套的 iOSAPP 复用存在的 JavaScript 代码,通过本教程,你将学到

  • JavaScriptCore framework
  • iOS 中怎样调用 JavaScript 代码
  • JavaScript 中怎样调用 iOS 的代码

你不必对JavaScript编程很有经验,如果这篇教程激起你学习JavaScript的兴趣,你可以看 Mozilla Developer Network的入门教程 不想看英文的话,

注 :廖雪峰的博客作为入门也是一个不错的选择,或者买这本书

这本书

0X0 开始

点击这里下载本篇教程所用到的代码,解压后,你将得到以下目录

  • Web目录,包含 HTMLCSS 文件
  • Native目录,iOS工程目录,本篇教程主要在这个目录下进行
  • js目录,这篇教程所用到的JavaScript代码

这个 APP 叫做 ShowTime 你可以输入价格,从 iTuns 中搜索电影,你可以打开在浏览器中打开 Web/index.html,然后输入价格,回车后,你将看到这个页面所呈现的内容;

iOS 端,打开工程,编译后,你将看到如下界面

Enough theory, let’s get to work!

0X2 调用JavaScript方法

回到 Xcode ,展开 Data 文件夹,打开 MovieService.swift ,这个类将请求并处理从 iTunes 返回的数据,现在,他们大部分是空的,我们的工作就是把这些方法实现了。

通常,MovieService 的工作流将是这样的

  • loadMoviesWithLimit(_:onComplete:) 取得对应的电影数据
  • parseResponse(_:withLimit:) 将借助于 JavaScript 代码来处理请求回来的数据。

第一步是获取电影列表,如果你熟悉 JavaScript 编程的话,一般我们使用 XMLHttpRequest 对象来进行网络请求。由于这个对象并不是 JavaScript 语言本身的对象,如果使用了这个对象,那我们将无法再 iOS APP 中的上下文中使用,所以,我们还是要用 native 来进行网络请求的

MovieService 类中,找到 loadMoviesWithLimit(_:onComplete:) 方法,改成下边这样

func loadMoviesWithLimit(limit: Double, onComplete complete: [Movie] -> ()) {
  guard let url = NSURL(string: movieUrl) else {
    print("Invalid url format: \(movieUrl)")
    return
  }

  NSURLSession.sharedSession().dataTaskWithURL(url) { data, _, _ in
    guard let data = data,
        jsonString = String(data: data, encoding: NSUTF8StringEncoding) else {
      print("Error while parsing the response data.")
      return
    }

    let movies = self.parseResponse(jsonString, withLimit:limit)
    complete(movies)

  }.resume()
}

这一段是用 NSURLSession 来获取电影列表,在把网络请求的响应信息传递个 JavaScript 代码前,你要有一个 JavaScript 可执行的上下文,首先,在 MovieService.swift 中加入下边的代码来导入 JavaScriptCore

import JavaScriptCore

然后在 MovieService 中定义如下属性

lazy var context: JSContext? = {
  let context = JSContext()

  // 1
  guard let
      commonJSPath = NSBundle.mainBundle().pathForResource("common", ofType: "js") else {
    print("Unable to read resource files.")
    return nil
  }

  // 2
  do {
    let common = try String(contentsOfFile: commonJSPath, encoding: NSUTF8StringEncoding)
    context.evaluateScript(common)
  } catch (let error) {
    print("Error while processing script file: \(error)")
  }

  return context
}()

这样就定义了一个懒加载的 JSContext 属性

  1. 加载了 common.js, 这里边包含了你想要访问的 JavaScript 代码
  2. 加载 js 之后, context 通过执行 context.evaluateScript() 来访问js的内容。传递的参数就是js文件的内容。

是时候来执行 JavaScript 方法了,还是在 MovieService.swift 这个类里边,找到 parseResponse(_:withLimit:) 函数,添加如下代码

func parseResponse(response: String, withLimit limit: Double) -> [Movie] {
  // 1
  guard let context = context else {
    print("JSContext not found.")
    return []
  }

  // 2
  let parseFunction = context.objectForKeyedSubscript("parseJson")
  let parsed = parseFunction.callWithArguments([response]).toArray()

  // 3
  let filterFunction = context.objectForKeyedSubscript("filterByLimit")
  let filtered = filterFunction.callWithArguments([parsed, limit]).toArray()

  // 4
  return []
}

我们来一步一步的看一下

  1. 首先,确保 context 属性被正确的初始化,如果在初始化的时候发生了错误,也就没有必要在执行下去了,比如说 common.js 不在 bundle 中。
  2. 你询问 context 对象来提供一个 parseJson 方法,就像先前提到的一样,查询的结果被包含到一个 JSValue 对象中,下边你将通过调用 callWithArguments(_:) 来执行 JavaScript 方法,传递一个数组过去,最后,再把 JSValue 对象转为数组。
  3. filterByLimit() 返回那些适合给定价格的电影列表。
  4. 现在已经获得了电影列表。但是这儿还缺失了一块代码,filtered 持有一个数组,我们应当把他映射为本地的 Movie 类型。

你可能发现在这里用 objectForKeyedSubscript() 有点古怪,很不幸,Swift 只能访问这些原始的方法,而不能把他们转为适当的脚本方法。但 OC 却可以使用方括号语法来来使用下标访问。

暴露 Native 代码

JavaScript 中运行 Native 代码的方法就是定义 block,他们将会被自动桥接到 JavaScript 方法中。 但有个小问题,这种方式只对 OC 有效,对 Swift 的闭包无效。为了执行闭包,你要执行以下两步

  1. 使用 @convention(block)Swift 闭包转为 OCblock
  2. 在你映射到 JavaScript 方法之前,应该转为 AnyObject

Movie.swift 添加 下边的代码

static let movieBuilder: @convention(block) [[String : String]] -> [Movie] = { object in
  return object.map { dict in

    guard let
        title = dict["title"],
        price = dict["price"],
        imageUrl = dict["imageUrl"] else {
      print("unable to parse Movie objects.")
      fatalError()
    }

    return Movie(title: title, price: price, imageUrl: imageUrl)
  }
}

这个闭包传递一个 JavaScript 数组(用字典代替),并且用它来构建 Movie 实例。

回到 MovieService.swiftparseResponse(_:withLimit:) 中,用一下代码替换 return 这一段

// 1
let builderBlock = unsafeBitCast(Movie.movieBuilder, AnyObject.self)

// 2
context.setObject(builderBlock, forKeyedSubscript: "movieBuilder")
let builder = context.evaluateScript("movieBuilder")

// 3
guard let unwrappedFiltered = filtered,
  let movies = builder.callWithArguments([unwrappedFiltered]).toArray() as? [Movie] else {
  print("Error while processing movies.")
  return []
}

return movies
  1. 使用 SwiftunsafeBitCast(_:_:) 方法把一个 block 转为一个 AnyObject
  2. 调用 contextsetObject(_:forKeyedSubscript:) 方法把 block 加载到 JavaScript 的运行时,然后,使用 evaluateScript() 得到 blockJavaScript 中的引用。
  3. 最后一步是通过 callWithArguments(_:) 执行 JavaScript 中的 block,传一个 JSValue 的数组作为参数。返回的参数将是一个包含 Movie 对象的数组。

是时候看看你的代码的效果了。编译并运行,输入价格之后回车,你将看到如下界面。

恭喜你,现在已经创建了一个可以浏览电影的超棒 APP,并且重用了用不同语言编写的已经存在的代码。

这就是无缝用户体验

你可以在这里下载本教程完整的代码。

如果你想学习更多的关于 JavaScriptCore 的内容, 请参看 WWDC 2013 Session 615

如有翻译不足的地方,还望多多指正,谢谢!!!

相关文章

网友评论

    本文标题:【译】JavaScriptCore Tutorial for i

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