美文网首页IosiOS程序员
(Swift) iOS Apps with REST APIs(

(Swift) iOS Apps with REST APIs(

作者: CD826 | 来源:发表于2016-06-30 18:56 被阅读328次

    本文将继续前面的教程,讲解如何将REST API获取JSON格式的数据转换为Swift对象。

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

    在Alamofire的GET与POST调用中使用强类型

    我们使用Alamofire进行REST请求。现在让我们清理一下,通过把JSON映射到强类型类,从而构建更高级别的抽象。这将使我们的代码组织性更好,也使得我们不需要一次记住太多代码的细节。

    首先,我们需要构建一个类来处理Post对象的类。创建的类为了方便进行调试,需要具有以下几个属性,一个Post对象构造函数,一个描述方法可以打印出对象的所有属性:

      class Post {
        var title:String? 
        var body:String? 
        var id:Int?
        var userId:Int?
        required init?(aTitle: String?, aBody: String?, anId: Int?, aUserId: Int?) { 
          self.title = aTitle
          self.body = aBody
          self.id = anId
          self.userId = aUserId 
        }
    
        func description() -> String { 
          return "ID: \(self.id)" +
            "User ID: \(self.userId)" + 
              "Title: \(self.title)\n" + 
               "Body: \(self.body)\n"
        } 
      }
    

    我们将使用路由器来创建URL请求。它可以装配请求信息,包括HTTP方法和URL,并在报头中附加相应的参数。在URL请求和JSON处理方面,我们不需要对路由做任何改变。它不需要知道Post对象的任何信息。

    接下来我们创建一个PostRouter.swift文件,作为我们的路由器:

      import Alamofire
      
      enum PostRouter: URLRequestConvertible {
        static let baseURLString = "http://jsonplaceholder.typicode.com/"
        
        case Get(Int)
        case Create([String: AnyObject])
        case Delete(Int)
        
        var URLRequest: NSMutableURLRequest {
          var method: Alamofire.Method {
            switch self {
              case .Get
                return .GET
              case .Create
                return .POST
              case .Delete
                return .DELETE
            }
          }
          
          let result: (path: String, parameters: [String: AnyObject]?) = {
            switch self {
              case .Get(let postNumber):
                return ("posts/\(postNumber)", nil)
              case .Create(let newPost):
                return ("posts", newPost)
              case .Delete(let postNumber)
                return ("posts/\(postNumber)", nil)
            }
          }()
          
          let URL = NSURL(string: PostRouter.baseURLString)!
          let URLRequest = NSURLRequest(URL: URL.URLByAppendingPathComponent(result.path))
          
          let encoding = Alamofire.ParameterEncoding.JSON
          let (encodedRequest, _) = encoding.encode(URLRequest, parameters: result.parameters)
          
          encodedRequest.HTTPMethod = method.rawValue
          
          return encodedRequest;
        }
      }
    

    当进行API调用时,我喜欢进行逆向工作。开始调用的时候,我们喜欢先搞清楚它们时怎么样工作的。首先,我们希望能够通过ID的值可以得到相应的帖子。

    我们可以在视图控制器的viewWillAppear方法中进行测试:

      override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        
        // MARK: 获取ID为1的Post
        Post.postByID(1, completionHandler: { result in
          if let error = result.error {
            // 如果在调用中出现了错误,我们需要进行处理
            print("调用/posts/1时出现错误")
            print(error)
            return
          }
          guard let post = result.value else {
            print("调用/posts/1时出现错误:返回值为空")
            return
          }
          
          // 调用成功
          print(post.getDescription())
          print(post.title)
        })
      }
    

    postByID有相应的完成处理程序。与前面所编写代码的不同是,我们不再用一个函数编写完成处理程序,而是在方法中直接进行处理。当我们实现postByID时我们将看到它们是如何工作的,以及当函数怎么样来调用完成处理程序来处理返回的结果。

    我们使用完成处理程序,可以让程序异步执行。这里你或许注意到整个程序中没有URL、没有请求,也没有JSON解析。它是由Post来完成,而不是在抽象的底层来处理。

    我们也希望能够新建一个Post,并将它发送到服务器。这里我们将newPost.save方法中使用尾随闭包来进行处理,所以我们可以在代码中删除completionHandler标签:

      // MARK: 保存
      // 创建Post
      guard let newPost = 
        Post(aTitle: "First Psot", aBody: "I iz first", anId: nil, aUserId: 1) else {
        print("错误: newPost不是一个Post对象")
        return
      }
      newPost.save { result in
        if let error = result.error {
          // 如果在调用中出现了错误,我们需要进行处理
          print("调用 POST /posts时出现错误")
          print(error)
          return
        }
        guard let post = result.value else {
          print("调用 POST /posts出现错误,返回值为空")
          return
        }
        
        // 调用成功
        print(post.description())
        print(post.title)
      }
    

    我们这里将Post的创建和保存分开,创建(Post(...))是在本地,而保存(newPost.save(...))则是在服务器中执行。另外,这里将PostID设置为空,那是因为该值是由服务器进行分配的。

    接下来,我们对Alamofire进行设置,并看看Post的调用是如何来完成的。首先我们看看GET请求(这里我们已使用顺手的路由来创建URL请求):

      Alamofire.request(PostRouter.Get(1))
        .responseJSON { response in 
          // ...
        }
    

    使用.responseObject来代替.responseJSON会不会更好呢?Alamofire是允许我们自己定义响应序列化(response serializer)处理,这样我们就可以将API调用的结果转换成任何我们想要的。最直接的就是将返回的JSON序列化为一个对象。

    响应序列化可以将URL请求返还的结果转换为我们需要型式。默认的,URL请求的返回是一个NSData,但我们愿意使用JSON或者一个对象进行相关处理。

    要创建一个序列化对象,需要扩展Alamofire.Request。我们将新建一个文件,名称为:AlamofireRequest+JSONSerializable.swift。如下面所示,我们先将逐步了解它是如何运作的:

     import Foundation
     import Alamofire
     import SwiftyJSON
     
     extension Alamofire.Request {
       public func responseObject<T: ResponseJSONObjectSerializable>(completionHandler:
         Response<T, NSError> -> Void) -> Self {
         let serializer = ResponseSerializer<T, NSError> { request, response, data, error in
           guard error == nil else { 
             return .Failure(error!)
           }
           guard let responseData = data else {
             let failureReason = "无法进行对象序列化,因为输入的数据为空。" 
             let error = Error.errorWithCode(.DataSerializationFailed, failureReason:
               failureReason) 
             return .Failure(error)
           }
           
           let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments) 
           let result = JSONResponseSerializer.serializeResponse(request, response,
             responseData, error)
           
           switch result {
           case .Success(let value):
             let json = SwiftyJSON.JSON(value) 
             if let object = T(json: json) {
               return .Success(object) 
             } else {
               let failureReason = "无法通过JSON创建对象。"
               let error = Error.errorWithCode(.JSONSerializationFailed, failureReason:
                 failureReason) 
               return .Failure(error)
             }
           case .Failure(let error):
             return .Failure(error) 
           }
         }
         
         return response(responseSerializer: serializer, completionHandler: completionHandler) 
       }
     }
    

    responseObject<...>(...)作为Alamofire.Request的新的响应处理器。它与标准的response函数唯一的不同是,它使用了我们自定义的responseSerializer对返回数据进行序列化处理。因此,我们可以像下面这样来调用:

      Alamofire.request(PostRouter.Get(id))
        .responseObject{ (response: Response<Post, NSError>) in
          // Post相关处理
        }
    

    我们逐渐来了解responseObject这个函数。首先,我们从函数的声明开始:

      extension Alamofire.Request {
        public func responseObject<T: ResponseJSONObjectSerializable>(completionHandler:
          Response<T, NSError> -> Void) -> Self {
          ...
        }
      }
    

    所定义的函数名称为:responseObject<T>表示这是一个泛型方法,可以与不同类型的对象一起工作。<T: ResponseJSONObjectSerializable>表示,对象的类型必须实现ResponseJSONObjectSeriablizable协议(该协议是我们自己定义的)。定义这个协议,是为了能够让我们确定所传入对象的类型,必须实现一个参数是JSON的构造方法(init function)。

    responseObject函数中使用唯一的参数为completionHandler。正如你所想的,当我们解析JSON并创建了对象后(也就是说作为当前函数的完成处理程序)就会调用它。这样我们就可以异步进行处理,而调用者也不用等待,当我们处理完毕后就会通知它。

    完成处理程序也是只有一个参数Response<T, NSErroe>。这是Alamofire3中定义的响应结构,它帮助我们处理了一大堆的事情,把处理结果包装成Result结构(里面是我们T对象和/或错误),这样省去了我们过去处理(NSURLRequest?, NSHTTPURLResponse?, Result<T, NSError>)的一大堆工作。

    你可以把ResponseResult结构想象成一个包,这个包中是我们从请求的响应中获取数据,并把它序列化为我们所使用的格式。这就像我们去买东西。支付后,你得到几样东西:你购买的东西、找零和收据,或者错误的信息,如你的“卡被拒绝“,或”还差8毛钱"等。所有这些就组合成了你购买时的响应。

    你也可以想象为不论交易是否成功,都会有购买的物品和/或者错误信息。

    Alamofire的这些结构是类似的。Result包含了.Success.Failure,这样呢可以判断是否有错误。Response则封装了Result、你的原始请求及返回的原始数据。

    responseObject返回了Alamofire.Request对象。-> Self则是声明返回的类型。

    现在,我们来看一下`responseObject函数的结构:

      public func responseObject<T: ResponseJSONObjectSerializable>(completionHandler:
        Response<T, NSError> -> Void) -> Self {
        let serializer = ResponseSerializer<T, NSError> { (request, response, data, error) in
          // ...
        }
        
        return response(responseSerializer: serializer, completionHandler: completionHandler)
      }
    

    responseObject中,我们创建了一个响应序列化处理器,并且与泛型TNSError一起工作。该序列化处理器使用URL请求的返回(request, response, data, error)作为参数,使用Alamofire中定义的Result类型返回成功(包含已解析对象)或者失败(包含错误信息)。responseObject函数只是返回了我们刚刚创建的responseSerializer,并把参数的中的完成处理程序也返回,这样就可以在需要的地方使用了。

    现在,我们来看一下我们最终所实现的responseSerializer

      let serializer = ResponseSerializer<T, NSError> { request, response, data, error in 
        guard error == nil else {
          return .Failure(error!) 
        }
        guard let responseData = data else {
          let failureReason = "无法进行对象序列化,因为输入的数据为空。"
          let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)    
          return .Failure(error)
        }
        
        let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments) 
        let result = JSONResponseSerializer.serializeResponse(request, response,
          responseData, error)
    
        switch result {
        case .Success(let value):
          let json = SwiftyJSON.JSON(value) 
          if let object = T(json: json) {
            return .Success(object) 
          } else {
            let failureReason = "无法通过JSON创建对象。"
            let error = Error.errorWithCode(.JSONSerializationFailed, failureReason:
              failureReason) 
            return .Failure(error)
          }
        case .Failure(let error):
          return .Failure(error) 
        }
      }
    

    自定义响应序列化处理首先使用guard进行数据有效性检查。然后使用SwiftyJSON将数据解析为JSON格式。接下来就是通过JSON数据来创建指定的对象:

      let newObject = T(json: json)
    

    如果不能通过JSON数据创建响应的对象,那么我们将返回相应的错误。

    为了能够是序列化程序可以正常工作,我们需要定义一个ResponseJSONObjectSerializable协议:

      public protocol ResponseJSONObjectSerializable {
        init?(json: SwiftyJSON.JSON)
      }
    

    定义该协议就是能够确保要转换的类必须实现了响应的构造函数。这也就告诉了泛型对象需要实现哪些处理。在现在这个例子中,类必须实现一个能够从JSON数据对象进行构造的方法。下面我们在Post类中实现它:

      final class Post: ResponseJSONObjectSerializable {
        var title:String?
        var body:String?
        var id:Int?
        var userId:Int?
        
        ...
        
        required init?(json: SwiftyJSON.JSON) {
          self.title = json["title"].string
          self.body = json["body"].string 
          self.id = json["id"].int
          self.userId = json["userId"].int
        }
        
        ...
      }
    

    使用SwiftyJSON可以是非常方便把JSON中的内容解析为Post对象的属性。

    现在我们就可以在Post.postById()中使用我们自定义的序列化了:

      class Post {
        ...
       
        // MARK: API调用
        class func postByID(id: Int, completionHandler: (Result<Post, NSError>) -> Void) { 
          Alamofire.request(PostRouter.Get(id))
            .responseObject { (response: Response<Post, NSError>) in 
              completionHandler(response.result)
            }
        }
      }
    

    这就是GET请求中所使用的了。现在我们就可以调用非常棒的Post.postByID(1)方法了。

    但,是的,还有其它的需求。我们说过我们需要实现POST请求,把新建Post保存到服务上。

    这种情况下,Alamofire中没有特别的限制要求,所以我们只需要保证可以把Post数据正确的序列化为API调用的格式即可。

    Post类中,我们只需要实现一个方法可以将Post转换为键为字符串类型的Dictionary即可(为了方便我们称为json):

      func toJSON() -> Dictionary<String, AnyObject> { 
        var json = Dictionary<String, AnyObject>()
        if let title = title {
          json["title"] = title
        }
        if let body = body { 
          json["body"] = body
        }
        if let id = id {
          json["id"] = id
        }
        if let userId = userId { 
          json["userId"] = userId
        }
        return json
      }
    

    因为在Alamofire.Request中使用Dictionary作为参数类型,所以这里我们没有使用NSDictionary

    下面我们来完成Postsave()方法:

      // 创建
      func save(completionHandler: (Result<Post, NSError>) -> Void) {
        guard let fields:Dictionary<String, AnyObject> = self.toJSON() else {
          print("error: error converting newPost fields to JSON")
          return
        }
        Alamofire.request(PostRouter.Create(fields))
          .responseObject { (response: Response<Post, NSError>) in 
            completionHandler(response.result)
        }
      }
    

    小结

    嗯,就是这样了!现在我们可以很漂亮的获取和保存Post。更进一步的是,调用者也不需要知道Post是如何获取和保存。这样我们也可以很方便的将Alamofire替换为RESTKit,或者另外一个完全不同的API调用框架,并且还不用对视图控制器做任何改变。

    点击这里 获取本章代码。

    现在不需要担心如何解析复杂的JSON数据。先从最简单的字符串、数字和布尔字段开始。后面我们会对复杂的数据进行解析,如数组、日期等。你可以从简单的一两个需要自定义序列化的API调用开始,或者使用本章的代码。

    接下来我们将使用Alamofire来构建gists应用。我们构建我们所需要的API调用,并把结果显示到用户界面。用户界面包含一个表格视图,gists的详情页面,创建gists的表单页面及下拉刷新和滑动删除。最后我们讨论一下离线时该如何进行处理。

    相关文章

      网友评论

      • bf6662411ce1:这一节 有点不明白 序列化 是干嘛 只是把数据转换为对象吗?这样不是有很多的方法么--
        CD826:@丶K神 可以这么理解,这里所指的序列化的确是将返回的JSON数据转换成对象,在Alamofire中做主要是让代码变的整洁,实际在开发中我发现只有部分可以这么多,很多时候还是需要单独写转换处理

      本文标题:(Swift) iOS Apps with REST APIs(

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