Reputation: 5111
I am using Alamofire to make requests to a JSON API. I am trying to serialize a collection of Post objects that have an author object and a comments array inside of it.
I have done the following:
Step 1: Follow steps
Extended the Alamofire.Request
object and added the ResponseObjectSerializer
and ResponseCollectionSerializer
as explained in the documentation under Generic Response Object Serialization
Step 2: Add the following models
Post.swift
final class Post : ResponseObjectSerializable, ResponseCollectionSerializable {
let id: Int
let title: String
let body: String
let author: Author
let comments: [Comment]
required init?(response: NSHTTPURLResponse, representation: AnyObject) {
self.id = representation.valueForKeyPath("id") as Int
self.body = representation.valueForKeyPath("body") as String
self.title = representation.valueForKeyPath("title") as String
// What do I do with the author object
var authorObj: AnyObject? = representation.valueForKeyPath("author")
if (authorObj != nil) {
self.author = Author(response: response, representation: authorObj!)!
}
// What do I do with the comments Array?
self.comments = Comment.collection(response: response, representation: representation.valueForKeyPath("comments")!)
}
class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Post] {
var postList:[Post] = []
for p in representation as [AnyObject] {
postList.append(Post(response: response, representation: p)!)
}
return postList
}
}
Comment.swift
final class Comment : ResponseObjectSerializable, ResponseCollectionSerializable {
let id: Int
let body: String
required init?(response: NSHTTPURLResponse, representation: AnyObject) {
self.id = representation.valueForKeyPath("id") as Int
self.body = representation.valueForKeyPath("body") as String
}
class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Comment] {
var commentList:[Comment] = []
var commentArray = representation as [AnyObject]
for c in commentArray {
commentList.append(Comment(response: response, representation: c)!)
}
return commentList
}
}
Author.swift
final class Author : ResponseObjectSerializable {
let id: Int
let name: String
required init?(response: NSHTTPURLResponse, representation: AnyObject) {
self.id = representation.valueForKeyPath("id") as Int
self.name = representation.valueForKeyPath("name") as String
}
}
Step 3: The representation is a Builtin.RawPointer
(lldb) po representation
(instance_type = Builtin.RawPointer = 0x00007f8b9ae1d290 -> 0x000000010c7f4c88 (void *)0x000000010c7f4dc8: __NSArrayI)
Any suggestions?
Step 4: Here is how I am calling the code
class NetworkPostProvider {
typealias RequestsCollectionResponse = (NSError?, [Post]?) -> Void
class func all(onCompletion: RequestsCollectionResponse) {
var manager = Alamofire.Manager.sharedInstance
manager.session.configuration.HTTPAdditionalHeaders = [
"Authorization": NSUserDefaults.standardUserDefaults().valueForKey(DEFAULTS_TOKEN) as String
]
manager.request(.GET, BASE_URL + "/alamofire/nested")
.validate()
.responseCollection({ (req, res, requests:[Post]?, error) -> Void in
println("Request:")
println(req)
println("Response:")
println(res)
println(requests)
})
}
}
Upvotes: 1
Views: 4167
Reputation: 16643
There are definitely some issues with your parsing logic (mainly safety). I went through and re-created your scenario the best I could and reworked the portions that seemed problematic. I have managed to fix your parsing which I'm pretty sure is the issue. It looks like you're calling everything properly from the Alamofire perspective.
import Foundation
import Alamofire
@objc public protocol ResponseObjectSerializable {
init?(response: NSHTTPURLResponse, representation: AnyObject)
}
@objc public protocol ResponseCollectionSerializable {
class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Self]
}
extension Alamofire.Request {
public func responseObject<T: ResponseObjectSerializable>(completionHandler: (NSURLRequest, NSHTTPURLResponse?, T?, NSError?) -> Void) -> Self {
let serializer: Serializer = { (request, response, data) in
let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let (JSON: AnyObject?, serializationError) = JSONSerializer(request, response, data)
if response != nil && JSON != nil {
return (T(response: response!, representation: JSON!), nil)
} else {
return (nil, serializationError)
}
}
return response(serializer: serializer, completionHandler: { (request, response, object, error) in
completionHandler(request, response, object as? T, error)
})
}
public func responseCollection<T: ResponseCollectionSerializable>(completionHandler: (NSURLRequest, NSHTTPURLResponse?, [T]?, NSError?) -> Void) -> Self {
let serializer: Serializer = { (request, response, data) in
let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let (JSON: AnyObject?, serializationError) = JSONSerializer(request, response, data)
if response != nil && JSON != nil {
return (T.collection(response: response!, representation: JSON!), nil)
} else {
return (nil, serializationError)
}
}
return response(serializer: serializer, completionHandler: { (request, response, object, error) in
completionHandler(request, response, object as? [T], error)
})
}
}
final class Post : ResponseObjectSerializable, ResponseCollectionSerializable, Printable {
let id: Int
let title: String
let body: String
let author: Author
let comments: [Comment]
var description: String {
return "Post {id = \(self.id), title = \(self.title), body = \(self.body), author = \(self.author), comments = \(self.comments)}"
}
required init?(response: NSHTTPURLResponse, representation: AnyObject) {
let id = representation.valueForKeyPath("id") as? Int
let title = representation.valueForKeyPath("title") as? String
let body = representation.valueForKeyPath("body") as? String
var author: Author?
if let authorObject: AnyObject = representation.valueForKeyPath("author") {
author = Author(response: response, representation: authorObject)
}
var comments: [Comment]?
if let commentsObject: AnyObject = representation.valueForKeyPath("comments") {
comments = Comment.collection(response: response, representation: commentsObject)
}
if id != nil && body != nil && title != nil && author != nil && comments != nil {
self.id = id!
self.title = title!
self.body = body!
self.author = author!
self.comments = comments!
} else {
self.id = 0
self.title = ""
self.body = ""
self.author = Author(id: 0, name: "")
self.comments = [Comment]()
return nil
}
}
class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Post] {
var postList = [Post]()
for p in representation as [AnyObject] {
if let post = Post(response: response, representation: p) {
postList.append(post)
}
}
return postList
}
}
final class Comment : ResponseObjectSerializable, ResponseCollectionSerializable, Printable {
let id: Int
let body: String
var description: String {
return "Comment {id = \(self.id), name = \(self.body)"
}
init(id: Int, body: String) {
self.id = id
self.body = body
}
required init?(response: NSHTTPURLResponse, representation: AnyObject) {
let id = representation.valueForKeyPath("id") as? Int
let body = representation.valueForKeyPath("body") as? String
if id != nil && body != nil {
self.id = id!
self.body = body!
} else {
self.id = 0
self.body = ""
return nil
}
}
class func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Comment] {
var commentList = [Comment]()
var commentArray = representation as [AnyObject]
for c in commentArray {
if let comment = Comment(response: response, representation: c) {
commentList.append(comment)
}
}
return commentList
}
}
final class Author : ResponseObjectSerializable, Printable {
let id: Int
let name: String
var description: String {
return "Author {id = \(self.id), name = \(self.name)}"
}
init(id: Int, name: String) {
self.id = id
self.name = name
}
required init?(response: NSHTTPURLResponse, representation: AnyObject) {
let id = representation.valueForKeyPath("id") as? Int
let name = representation.valueForKeyPath("name") as? String
if id != nil && name != nil {
self.id = id!
self.name = name!
} else {
self.id = 0
self.name = ""
return nil
}
}
}
class NetworkPostProvider {
typealias RequestsCollectionResponse = (NSError?, [Post]?) -> Void
class func all(onCompletion: RequestsCollectionResponse) {
var manager = Alamofire.Manager.sharedInstance
manager.session.configuration.HTTPAdditionalHeaders = [
"Authorization": NSUserDefaults.standardUserDefaults().valueForKey("12345678") as String
]
let request = manager.request(.GET, "Some base url" + "/alamofire/nested")
request.validate()
request.responseCollection { (req, res, requests: [Post]?, error) in
println("Request:")
println(req)
println("Response:")
println(res)
println(requests)
}
}
class func forceLoadJSON() {
let path = NSBundle.mainBundle().pathForResource("stacked", ofType: "json")!
let data = NSData(contentsOfFile: path)!
if let json: AnyObject = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error: nil) {
println(json)
let fakeResponse = NSHTTPURLResponse(URL: NSURL(string: "")!, statusCode: 200, HTTPVersion: nil, headerFields: nil)!
let posts = Post.collection(response: fakeResponse, representation: json)
println("Posts: \(posts)")
} else {
println("Failed to load the posts")
}
}
}
The forceloadJSON
method recreates everything Alamofire will trigger once the request comes back from the server. The problem is that I can't actually call your server b/c you didn't provide the necessary credentials or URL to actually call it. If you want me to debug it further, I'll need that info. I'm almost positive though that your issue has already been resolved with all the changes I've made to the Post
, Comment
and Author
classes.
Another change I would suggest moving forward would be to use structs instead of classes. There's currently a bug in the Swift 1.1 compiler that doesn't allow you to return nil
in a failable initializer inside a class. This is most likely fixed in Swift 1.2 but I haven't checked yet.
The last thing I would change would be to modify the ResponseCollectionSerializable
function collection
to be able to return either an optional array or an array of optionals. This is because your collection method can fail to create instances since it is calling failable initializers. Currently it is burying those errors and you don't know if parsing ever failed. You previously would crash in that scenario (maybe that's what you want). I modified it so that it won't crash, but the error is then buried.
Upvotes: 4