`
cd826
  • 浏览: 129010 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

iOS Apps with REST APIs(二)

阅读更多

重要说明: 这是一个系列教程,非本人原创,而是翻译国外的一个教程。本人也在学习Swift,看到这个教程对开发一个实际的APP非常有帮助,所以翻译共享给大家。原教程非常长,我会陆续翻译并发布,欢迎交流与分享。

再另,原文发布在我的博客中:iOS Apps with REST APIs(二)

我已准备好到直接跳到了一些对你有用的代码。但是,苹果在iOS9中引入App Transport Security,虽然ATS是保护从你iPhone中发送和接受的数据,但对于开发人员来说的确有点头痛。

ATS要求使用SSL进行数据传输,这对实施来说是非常挑剔的。可悲的是,现在很多服务器是不满足这项要求的。所以,如果我们需要与访问这些服务器时该怎么解决呢?接下来我们会解决这个问题,因为后面讲解网络调用基础知识所使用的服务器是需要先解决这个问题的。

我们将为这种应用情况下的应用程序传输安全增加一个异常。虽然我们可以禁用ATS,但这样比只为一台服务器创建异常更安全。在本章我们所要访问的API是http://jsonplaceholder.typicode.com/

我们需要在项目的info.plist文件中增加一些键,以便创建该异常。我们会添加一个NSAppTransportSecurity的字典,该字典中包含NSExceptionDomains字典,存放服务器:jsonplaceholder.typicode.com(注意:没有结尾的斜杠,也没有http或https前缀)。在jsonplaceholder.typicode.com字典中增加一个布尔值的条目NSThirdPartyExceptionAllowsInsecureHTTPLoads,并把值设置为YES


这样,下面我们就可以正式开始编写代码了。

使用Swift进行简单的REST API调用

当今几乎每一个APP都会通过API获取或创建内容。在本书中,我们会使用一个非常强大的网络处理库Alamofire,当然你也可以使用NSURLSession快速的,不是那么优雅的执行异步数据请求任务。

Swift进行异步URL请求的方法如下:

  public func dataTaskWithRequest(request: NSURLRequest,    
    completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void)    
    -> NSURLSessionDataTask

该方法将向特定的URL发起一个请求,请求发送完毕后将停止运行。一旦得到对方服务器的响应(甚至是一个错误报告),相应的完成处理程序(completion handler)将被调用。我们可以在完成处理程序中对调用的结果进行处理,包括错误检查、将数据保存到本地、更新界面,等等。在实现dataTaskWithRequest章节我们会深入讨论完成处理程序。

最简单的例子就是一个GET请求。不过,我们需要一个API来进行测试,幸运的是JSONPlaceholder可以充当这个工具。

JSONPlaceholder是一个用来测试和制作原型的在线伪REST API。就像网页中的图像占位符。

JSONPlaceholder中有一些资源可以使用,类似于我们在很多应用中使用的:用户、帖子、照片、相册…等等。不过我们这里使用帖子(posts)。

首先,让我们先打印出第一个帖子的标题。我们可以通过发起一个到posts端点(endpoint)的GET请求,参数为帖子的ID,来获取一个帖子的详细信息。从http://jsonplaceholder.typicode.com/posts/我们可以知道第一个帖子的ID是1。下面就让我们获取它:

首先,我们设置URL请求:

  let postEndpoint: String = "http://jsonplaceholder.typicode.com/posts/1"
  guard let url = NSURL(string: postEndpoint) else {
    print("Error: cannot create URL")
    return
  }
  let urlRequest = NSURLRequest(URL: url)

guard关键字可以让我们检查所请求的URL是否有效。

接下来,使用NSURLSession发送请求:

  let config = NSURLSessionConfiguration.defaultSessionConfiguration()
  let session = NSURLSession(configuration: config)

然后,创建数据任务(data task):

  let task = session.dataTaskWithRequest(urlRequest, completionHandler: nil)

最后,发送请求(是的,这个方法名称有点怪异)

  task.resume()

现在我们将调起URL(通过urlRequest),并获得相应的结果(作为默认将使用GET请求)。因此我们必须实现一个完成处理程序对返回结果进行处理,以便完成相应的处理。

当你第一次运行时,完成处理程序可能会有点混乱。一方面,它是一个变量或参数;另一方面,它又是一堆代码。如果你以前没有用过这种程序(块或者闭包),你会觉得非常奇怪。

当你的应用需要花费一些时间进行任务处理,比如:API调用,并且在任务处理完毕后需要执行某些动作处理,如更新界面显示新的数据,这时候使用完成处理程序是超级方便的。接下来你会看到苹果的API,如dataTaskWithRequest中的完成处理程序,及后面我们在使用我们自己API时的完成处理程序。

dataTaskWithRequest中的完成处理程序的方法签名如下:

  completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void

这是一段代码块(->将告诉我们返回值类型,如果有的话)。它有三个参数:(NSData?, NSURLResponse?, NSError?)及返回类型:Void。对于一个具体的完成处理程序的内联代码如下:

  let task = session.dataTaskWithRequest(urlRequest, completionHandler: 
  { (data, response, error) in
    // 这里是完成处理程序的具体代码
  })  
  task.resumt()

注意,花括号中间的那段代码,有三个参数(data, response, error),与完成处理程序中声明的参数(NSData?, NSURLResponse?, NSError?)是对应的。你可以在代码段显示的指出,也可以省略,这个不是必须的,因为编译器可以推导出来。所要记住的是,代码不仅仅是给电脑执行的,更多的时候是给我们自己看,因此,不要弄得太晦涩。

  let task = session.dataTaskWithRequest(urlRequest, 
    completionHandler:{(data: NSData?, response: NSURLResponse?, 
      error: NSError?) in 
    // 这里是完成处理程序的具体代码
    print(response)
    print(error)
  })
  task.resume()

有点混乱是吗!实际上你完全可以删除completionHandler声明,直接将代码放在函数调用的后面。这完全和上面的代码是一样的,这种写法在Swift是非常常见的(译者注:这种在Swift中称为尾随闭包):

  let task = session.dataTaskWithRequest(urlRequest) { (data, response, error) in 
    // 完成处理程序将放在这里
    print(response)
    print(error)
  }
  task.resume()

如果将参数名称设置为_就是告诉编译器,你会忽略掉该参数:

  let task = session.dataTaskWithRequest(urlRequest) { (data, _, error) in
    // 这时候将不能够打印response,因为我们忽略了该参数
    print(error)  
  }
  task.resume()

我们也可以将完成处理函数声明为一个变量,然后把这个变量传给dataTaskWithRequest。这样,当我们需要在多个地方调用时会非常方便。在后面实现OAuth2.0认证流程时我们会使用这种方式,因为还有很多要处理,所以现在我们会在调用失败处理的时候使用。

下面的代码就是完成处理程序的变量方式:

  let myCompletionHandler: (NSData?, NSURLResponse?, NSError?) -> Void = {
    (data, response, error) in 
       // 完成处理代码
       print(response)
       print(error)
  }
  let task = session.dataTaskWithRequest(urlRequest, 
    completionHandler: myCompletionHandler)
  task.resume()

那么上面这段代码发生了什么呢?嗯,当我们调用dataTaskWithRequest函数后,它不会马上被执行。但,苹果在实现dataTaskWithRequest会像下面调用:

  public func dataTaskWithRequest(request: NSURLRequest,
    completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void)
    -> NSURLSessionDataTask {
    // 进行URL请求
    // 等待请求结果
    // 检查返回是否有错误,并进行处理
    completionHandler(data, response, error)
    // 返回 data task
 }

当然,在你的代码中不用像上面那样去写,因为dataTaskWithRequest中已经实现。事实上,可能会有几个类似上面的调用来处理成功或者失败。而完成处理程序仅仅就是待在那里,等待dataTaskWithRequst处理完毕后调用。

那么什么是完成处理程序呢?简单来说,就是我们可以用来在一个动作完成后再进行某些处理。比如,像上面我们用来打印调用的结果,及可能出现的错误,以便验证API调用是否正常工作。让我们回到我们例子中,在完成处理程序写一点有用的代码。然后,代码将变成:

  let task = session.dataTaskWithRequest(urlRequest, completionHandler:
    { (data, response, error) in
    // 做一些有意义的事
  })
  task.resume()

现在我们可以访问3个对象:请求的响应,请求的返回数据及请求的错误(如果有的话)。下面我们首先进行错误检查,然后给出如何得到我们想要的数据:第一篇帖子的标题。接下来我们将:

  • 确定我们得到了数据,并且没有错误;
  • 尝试将数据转换为JSON格式(因为,API返回的数据格式是JSON);
  • 获取帖子的标题并打印。

注意,这里你需要在代码中增加import Foundation,以便可以使用NSJSONSerialization

  let task = session.dataTaskWithRequest(urlRequest, completionHandler): {
    (data, response, error) in 
    guard let responseData = data else {
      print("错误: 没有接收到返回数据")
      return
    }
    guard error == nil else {
      print("获取/posts/1时有错误")
      print(error)
      return
    }

    // 将返回的数据解析为JSON格式
    let post: NSDictionary
    do {
      post = try NSJSONSerialization.JSONObjectWithData(responseData, 
        options:[]) as! NSDictionary
    } catch {
      print("将数据转换为JSON时出错")
      return
    }

    // 现在我们可以访问post
    print("帖子:" + post.description)

    // 因为post是一个dictionary(字典),因此我们可以通过"title"来获取帖子标题
    if let postTitle = post["title"] as? String {
      print("帖子标题: " + postTitle)
    }
  })
  task.resume()

运行后,输出如下:

  帖子: {
    body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderi\
t molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"; 
    id = 1;
    title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit";
    userId = 1;
  }
  帖子标题: sunt aut facere repellat provident occaecati excepturi optio reprehenderit

这个有点冗长,假如你仅仅需要快速发起一个GET请求,并且不需要身份验证,那么就是这样。

但如果请求的方法类型不是GET,那么你需要使用一个可变的NSURLRequest,通过NSURLRequest就可以设置请求的方法了:

  let postEndpoint: String = "http://jsonplaceholder.typicode.com/posts"
  let postUrlRequest = NSMutableURLRequest(URL: NSURL(string: postEndpoint)!)
  postUrlRequest.HTTPMethod = "POST"

这样我们就是将新建的帖子内容设置给请求的HTTPBody中:

  let newPost: NSDictionary = ["title": "Frist Post", "body": "I is fisrt", 
    "userId":1]
  do {
    let jsonPost = try NSJSONSerialization.dataWithJSONObject(newPost, options:[])
    postUrlRequest.HTTPBody = jsonPost
  } catch {
    print("错误:无法创建帖子的JSON对象")
  }

现在我们就可以执行请求了(假设我们现在还持有前面创建的会话信息):

  let task = session.dataTaskWithRequest(postUrlRequest, completionHandler: nil)
  task.resume()

如果上面的代码正常工作,那么我们在发送后将会得到新帖子的ID。因为这只是用于测试,JSONPlaceholder会让你做各种REST请求(GET,POST,PUT,PATCH,DELETE及OPTIONS),但不会真正改变数据。所以,当我们新建了帖子后,我们会得到新帖子的ID,以确认我们程序工作正常,但它实际上不会保存到数据库中,所以我们是不能访问它的。

  let newPost: NSDictionary = ["title": "Frist Post", 
    "body": "I is fisrt", "userId":1]
  do {
    let jsonPost = try NSJSONSerialization.dataWithJSONObject(newPost, 
      options:[])
    postUrlRequest.HTTPBody = jsonPost

    let config = NSURLSessionConfiguration.defaultSessionConfiguration()
    let session = NSURLSession(configuration: config)

    let task = session.dataTaskWithRequest(postsUrlRequest, 
      completionHandler: { (data, response, error) in
      guard let responseData = data else {
        print("错误:没有接收到数据")
        return
      }
      guard error == nil else {
        print("调用POST /posts 时出现错误")
        print(error)
        return
      }

      // 解析成JSON
      let post: NSDictionary
      do {
        post = try NSJSONSerialization.JSONObjectWithData(responseData,
          options:[]) as! NSDictionary
      } catch {
        print("解析POST /posts的返回时出错")
        return
      }

      // 打印帖子内容
      print("帖子:" + post.description)

      // 打印帖子的ID
      if let postID = post["id"] as? Int {
      print("帖子ID:\(postID)")
    }
  })
  task.resume()
}

删除帖子的代码非常类似(不需要帖子创建后的代码):

  let firstPostEndpoint: String = "http://jsonplaceholder.typicode.com/posts/1"
  let firstPostUrlRequest = NSMutableRequest(URL: NSURL(string: firstPostEndpoint)!)
  firstPostUrlRequest.HTTPMethod = "DELETE"

  let config = NSURLSessionConfiguration.defaultSessionConfiguration()
  let session = NSURLSession(configuration: config)

  let task = session.dataTaskWithRequest(firstPostUrlRequest,
    completionHandler: { (data, response, error) in
    guard let _ = data else {
      print("调用DELEE /posts/1 时出现错误")
      return
    }
  })
  task.resume()

这是使用Swift调用REST API快速的、肮脏的方式。这里会有几个陷阱:因为我们假设了调用会成功得到返回数据,并且数据的格式是我们所期望的。如果这会引起一个问题,一旦返回值的类型不是字典类型,那么整个App就会崩溃。

  post = try NSJSONSerialization.JSONObjectWithData(responseData, 
    options:[] as! NSDictionary)

当然,我们可以通过检查返回的数据确保不为空并且是一个字典类型(但,这会让冗长变的很快),或者我们使用SwiftyJSON来代替:

  // 解析成JSON
  let post = JSON(data: responseData)  
  if let postID = post["id"].int {
    print("帖子ID:\(postID)")
  }

SwiftyJSON在每一步都会进行检查,如果post为空,或者post["id"]为空,则postID也会为空。这时候.int将返回一个可选的,就像Int?。如果你确保值不会为空,那么可以使用.intValue,而不是得到一个可选值。

SwiftyJSON不仅仅可以处理整数。下面列出了如何处理stringdoubleboolean:

let title = myJSON["title"].string
let cost = myJSON["cost"].double
let isPurchased = myJSON["purchased"].bool

如果JSON对象是一个数组(比如:帖子列表),那么你可以通过数组索引来访问响应的属性,如:

let thirdPostTitle = posts[3]["title"].string

到目前为止,这些代码非常冗长,而且也没有什么抽象。你本来是在处理帖子的功能,却不得不写一大堆代码来处理网络请求及数据处理。看来Alamofire将是我们一个比较好的选择:

Alamofire.request(.GET, postEndpoint).responseJSON { response in
  // 获取错误
  print(response.result.error)
  // 获取序列化后的数据(如JSON)
  print(response.result.value)
  // 获取原始数据
  print(response.data)
  // 获取NSHTTPURLResponse
  print(response.response)
}

获取GitHub上的代码:REST gists

 

大家可以关注我的微信公众号(CD826Workshop)来进行交流。

分享到:
评论

相关推荐

    毕业设计&课设_CUMT 信息安全专业毕业设计:基于区块链的能源交易系统,含架构、部署等多方面详细介绍.zip

    毕业设计&课设_CUMT 信息安全专业毕业设计:基于区块链的能源交易系统,含架构、部署等多方面详细介绍.zip

    用Python分析文本数据项目

    用Python分析文本数据项目

    基于SpringBoot的养老院管理系统源码数据库文档.zip

    基于SpringBoot的养老院管理系统源码数据库文档.zip

    AshampooUnInstaller v15.00.22 Portable一款强大的卸载工具,彻底、智能著称阿香婆强制卸载软件.rar

    阿香婆软件强制卸载软件 Ashampoo UnInstaller 是一款强大的卸载工具,彻底、智能著称,如果您选择使用Ashampoo安装程序,它会自动分析软件从开始安装到结束的全过程,并为该软件建立一个日志,以便以后更彻底的卸载它,Ashampoo UnInstaller 全面提速!程序采用了新的技术,速度得到了全面提高。Windows 默认应用现在也可以像其它无用程序和恶意软件一样被无忧卸载。摆脱工具栏、插件和其它隐藏的追踪软件,享受安全快捷的上网体验!新的快照比较功能,可以非常简单的创建安装日志,这是旧版本 UnInstaller 粉丝最急需的!新的界面,便捷的批量卸载,以及内置的在线搜索,Ashampoo UnInstaller 一定会让你惊叹无比。快来体验更清晰、更详尽、更快速的 Ashampoo UnInstaller !Ashampoo UnInstallerAshampoo UnInstaller彻底删除无用程序的所有痕迹! 让安装、试用、卸载程序更加轻松。只需点几下,无用的软件、浏览器扩展和工具栏就能从你的硬盘中

    基于java的公交车信息管理系统开题报告.docx

    基于java的公交车信息管理系统开题报告

    基于java的网络书店系统的开题报告.docx

    基于java的网络书店系统的开题报告

    forge-1.20.1-47.3.0-installer.jar

    forge-1.20.1-47.3.0-installer

    实例-文本进度条源代码

    python

    基于springboot云平台的信息安全攻防实训平台源码数据库文档.zip

    基于springboot云平台的信息安全攻防实训平台源码数据库文档.zip

    基于SpringBoot的房屋租赁系统源码数据库文档.zip

    基于SpringBoot的房屋租赁系统源码数据库文档.zip

    毕业设计&课设_家居物联网毕业设计项目:利用多种设备实现,含架构、目标.zip

    毕业设计&课设_家居物联网毕业设计项目:利用多种设备实现,含架构、目标.zip

    基于springboot的非学勿扰学习交流平台源码数据库文档.zip

    基于springboot的非学勿扰学习交流平台源码数据库文档.zip

    网络安全教程:基础知识到高级概念全面解读

    内容概要:本文详细介绍了网络安全领域的基础知识和高级概念,涵盖了网络安全概述、网络架构与协议、操作系统安全、网络攻击与防御、密码学与安全协议、身份认证与访问控制、系统漏洞与补丁管理、网络安全实践及发展趋势与挑战等内容。 适合人群:信息安全专业人员、IT运维人员、网络安全工程师、系统管理员。 使用场景及目标:本文不仅帮助读者理解网络安全的理论知识,还能指导实际操作,适用于日常网络安全管理和应急响应。目标在于增强个人和企业对网络安全的认识,提升防护能力。 其他说明:网络安全是一个不断发展变化的领域,文中提到的内容和技术应及时跟进最新的发展动态,确保网络安全的有效性。

    java毕设项目之基于SpringBoot的特殊儿童家长教育能力提升平台(源码+说明文档+mysql).zip

    环境说明:开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7 数据库工具:Navicat 开发软件:eclipse/myeclipse/idea Maven包:Maven 浏览器:谷歌浏览器。 项目经过测试均可完美运行

    精选微信小程序源码:仿京东白条小程序(含源码+源码导入视频教程&文档教程,亲测可用)

    微信小程序是一种轻量级的应用开发平台,主要针对移动端,由腾讯公司推出,旨在提供便捷的、无需下载安装即可使用的应用服务。"京东白条"是京东金融推出的一种信用消费产品,用户可以在京东商城购物时享受先消费后付款的便利。这款"微信小程序京东白条小程序源码"包含了实现这一功能的小程序源代码,以及相应的导入教程,对于开发者来说,是一个有价值的参考资料。 源码是程序开发的基础,它包含了开发者编写的控制程序行为的指令。在这个案例中,源码可能包括了与京东白条接口交互的逻辑,用户界面设计,以及数据处理等功能。通过分析这些源码,开发者可以学习如何集成京东的API,实现白条的查询、支付和管理等操作。 "源码导入视频教程"和"源码导入文档教程"是辅助开发者理解和使用源码的重要资料。视频教程通常以直观的方式展示源码的导入步骤和环境配置,帮助开发者快速上手。文档教程则可能包含了详细的步骤说明,解决常见问题的技巧,以及开发过程中的注意事项,对于初学者来说尤其有价值。 微信小程序的开发涉及到以下几个关键技术点: 1. **WXML(Weixin Markup Language)**:这是微信小程序的视图层语言,

    数字孪生赋能智慧城市大脑建设方案PPT(65页).pptx

    市大脑的基础平台是支撑其高效运行的关键。该平台通过整合电子政务网、视联网、互联网等多网资源,以及云计算、大数据、人工智能等先进技术,打造了一个统一、开放、可扩展的底层架构。在这个基础上,城市大脑能够接入海量数据,包括但不限于视频监控、交通流量、环境监测、民生服务等领域的实时数据,并进行高效处理和分析。这些数据经过清洗、转换、开发后,形成了丰富的数据资源池,为城市治理提供了坚实的数据支撑。 在数据资源的基础上,智慧城市大脑进一步构建了算法服务平台和融合业务应用系统。算法服务平台集成了多种先进的视频分析算法和人工智能算法,如人脸识别、车辆识别、城市问题智能识别等,这些算法能够实时分析视频流和数据流,快速识别城市运行中的问题和异常。同时,融合业务应用系统则将这些算法与城市治理的各个领域相结合,如交通、环保、旅游、医疗等,形成了多个具有实战价值的应用场景。例如,在交通领域,城市大脑能够通过实时分析交通流量和路况信息,优化信号灯控制策略,缓解交通拥堵;在环保领域,则能够实时监测空气质量和水质情况,及时预警环境污染事件。 数字驾驶舱是智慧城市大脑的又一亮点。它作为城市治理能力现代化的新抓手,实现了数据一屏展示、指标一屏分析、指挥一屏联动、场景一屏闭环和治理一屏透视。通过数字驾驶舱,决策者可以直观地看到城市运行的全貌,及时获取关键信息,做出准确的判断和决策。同时,数字驾驶舱还能够根据用户的需求进行个性化配置,提供多种数据处理和分析工具,帮助用户深入挖掘数据价值,提升城市治理的精细化和智能化水平。 智慧城市大脑的建设成效显著。它不仅提高了城市治理的效率和准确性,还带来了显著的社会效益和经济效益。通过优化资源配置、降低运营成本、提升服务质量等方式,智慧城市大脑为城市居民提供了更加便捷、高效、舒适的生活环境。同时,它也为城市的可持续发展注入了新的动力,推动了产业升级和经济转型。可以说,智慧城市大脑已经成为了未来城市发展的重要方向和趋势,它将引领我们走向一个更加智慧、绿色、宜居的城市新时代。 总的来说,智慧城市大脑是一个集数据、算法、应用为一体的综合性解决方案,它通过高度集成和智能分析,实现了城市治理的精细化和智能化。在未来的发展中,随着技术的不断进步和应用场景的不断拓展,智慧城市大脑将会发挥出更加巨大的潜力和价值,为城市的可持续发展和社会的全面进步做出更大的贡献。

    毕业设计&课设_景区购票系统(含更新情况、数据字典及多种语言实现).zip

    毕业设计&课设_景区购票系统(含更新情况、数据字典及多种语言实现).zip

    上传一个考研冲刺资源dssdf

    上传一个【考研冲刺】资源

    C0858 手机之家(1页).Zip

    C0858 手机之家(1页).Zip

Global site tag (gtag.js) - Google Analytics