Skip to content

Instantly share code, notes, and snippets.

@cmoulton
Last active December 4, 2018 04:52
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save cmoulton/98654ec7c624f6d96e4e to your computer and use it in GitHub Desktop.
Save cmoulton/98654ec7c624f6d96e4e to your computer and use it in GitHub Desktop.
Demo code for Strongly-Typed GET and POST Calls With Alamofire at https://grokswift.com/strongly-typed-api-calls/. Uses Swift 2.0, SwiftyJSON 2.3.0, and Alamofire 3.0. (For Swift 3, see https://gist.github.com/cmoulton/733356fd0a29411153155606c0cdc2ba)
public protocol ResponseJSONObjectSerializable {
init?(json: SwiftyJSON.JSON)
}
class Todo: ResponseJSONObjectSerializable {
var title: String?
var id: Int?
var userId: Int?
var completed: Int?
required init?(aTitle: String?, anId: Int?, aUserId: Int?, aCompletedStatus: Int?) {
self.title = aTitle
self.id = anId
self.userId = aUserId
self.completed = aCompletedStatus
}
required init?(json: [String: AnyObject]) {
self.title = json["title"] as? String
self.id = json["id"] as? Int
self.userId = json["userId"] as? Int
self.completed = json["completed"] as? Int
}
required init?(json: SwiftyJSON.JSON) {
self.title = json["title"].string
self.id = json["id"].int
self.userId = json["userId"].int
self.completed = json["completed"].int
}
func toJSON() -> [String: AnyObject] {
var json = [String: AnyObject]()
if let title = title {
json["title"] = title
}
if let id = id {
json["id"] = id
}
if let userId = userId {
json["userId"] = userId
}
if let completed = completed {
json["completed"] = completed
}
return json
}
func description() -> String {
return "ID: \(self.id)" +
"User ID: \(self.userId)" +
"Title: \(self.title)\n" +
"Completed: \(self.completed)\n"
}
// MARK: URLs
class func endpointForID(id: Int) -> String {
return "http://jsonplaceholder.typicode.com/todos/\(id)"
}
class func endpointForTodos() -> String {
return "http://jsonplaceholder.typicode.com/todos/"
}
class func todoByIDNoAlamofire(id: Int, completionHandler: (Todo?, NSError?) -> Void) {
let endpoint = Todo.endpointForID(id)
guard let url = NSURL(string: endpoint) else {
print("Error: cannot create URL")
let error = NSError(domain: "TodoClass", code: 1, userInfo: [NSLocalizedDescriptionKey: "cannot create URL"])
completionHandler(nil, error)
return
}
let urlRequest = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
completionHandler(nil, error)
return
}
guard error == nil else {
print("error calling GET on /todos/1")
print(error)
completionHandler(nil, error)
return
}
// parse the result as JSON, since that's what the API provides
do {
if let todoJSON = try NSJSONSerialization.JSONObjectWithData(responseData, options: []) as? [String: AnyObject] {
if let todo = Todo(json: todoJSON) {
// created a TODO object
completionHandler(todo, nil)
} else {
// couldn't create a todo object from the JSON
let error = NSError(domain: "TodoClass", code: 2, userInfo: [NSLocalizedDescriptionKey: "Couldn't create a todo object from the JSON"])
completionHandler(nil, error)
}
}
} catch {
print("error trying to convert data to JSON")
let error = NSError(domain: "TodoClass", code: 3, userInfo: [NSLocalizedDescriptionKey: "error trying to convert data to JSON"])
completionHandler(nil, error)
return
}
})
task.resume()
}
func saveNoAlamofire(completionHandler: (Todo?, NSError?) -> Void) {
let todosEndpoint = Todo.endpointForTodos()
guard let todosURL = NSURL(string: todosEndpoint) else {
let error = NSError(domain: "TodoClass", code: 4, userInfo: [NSLocalizedDescriptionKey: "Error: cannot create URL"])
completionHandler(nil, error)
return
}
let todosUrlRequest = NSMutableURLRequest(URL: todosURL)
todosUrlRequest.HTTPMethod = "POST"
let newTodoAsJSON = self.toJSON()
let jsonTodo: NSData
do {
jsonTodo = try NSJSONSerialization.dataWithJSONObject(newTodoAsJSON, options: [])
todosUrlRequest.HTTPBody = jsonTodo
} catch {
let error = NSError(domain: "TodoClass", code: 5, userInfo: [NSLocalizedDescriptionKey: "Error: cannot create JSON from todo"])
completionHandler(nil, error)
return
}
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(todosUrlRequest, completionHandler: {
(data, response, error) in
guard let responseData = data else {
print("Error: did not receive data")
return
}
guard error == nil else {
print("error calling POST on /todos/1")
print(error)
return
}
do {
if let todoJSON = try NSJSONSerialization.JSONObjectWithData(responseData,
options: []) as? [String: AnyObject] {
if let todo = Todo(json: todoJSON) {
// created a TODO object
completionHandler(todo, nil)
} else {
// couldn't create a todo object from the JSON
let error = NSError(domain: "TodoClass", code: 3, userInfo: [NSLocalizedDescriptionKey: "Couldn't create a todo object from the JSON"])
completionHandler(nil, error)
}
}
} catch {
print("error parsing response from POST on /todos")
return
}
})
task.resume()
}
// MARK: API Calls
class func todoByID(id: Int, completionHandler: (Result<Todo, NSError>) -> Void) {
Alamofire.request(TodoRouter.Get(id))
.responseObject { (response: Response<Todo, NSError>) in
completionHandler(response.result)
}
}
// POST / Create
func save(completionHandler: (Result<Todo, NSError>) -> Void) {
let fields = self.toJSON()
Alamofire.request(.POST, endpointForTodos(), parameters: self.toJSON(), encoding: .JSON)
.responseObject { (response: Response<Todo, NSError>) in
completionHandler(response.result)
}
}
}
extension Alamofire.Request {
public func responseObject<T: ResponseJSONObjectSerializable>(completionHandler: Response<T, NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<T, NSError> { request, response, data, error in
guard error == nil else {
return .Failure(error!)
}
guard let responseData = data else {
let failureReason = "Array could not be serialized because input data was nil."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let result = JSONResponseSerializer.serializeResponse(request, response, responseData, error)
if result.isSuccess {
if let value = result.value {
let json = SwiftyJSON.JSON(value)
if let newObject = T(json: json) {
return .Success(newObject)
}
}
}
let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: "JSON could not be converted to object")
return .Failure(error)
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
public func responseArray<T: ResponseJSONObjectSerializable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<[T], NSError> { request, response, data, error in
guard error == nil else {
return .Failure(error!)
}
guard let responseData = data else {
let failureReason = "Array could not be serialized because input data was nil."
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)
var objects: [T] = []
for (_, item) in json {
if let object = T(json: item) {
objects.append(object)
}
}
return .Success(objects)
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
import UIKit
import Foundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// MARK: POST
// Create new todo
guard let newTodo = Todo(aTitle: "Frist todo", anId: nil, aUserId: 1, aCompletedStatus: 0) else {
print("error: newTodo isn't a Todo")
return
}
newTodo.save { result in
guard result.error == nil else {
// got an error in getting the data, need to handle it
print("error calling POST on /todos/")
print(result.error)
return
}
guard let todo = result.value else {
print("error calling POST on /todos/: result is nil")
return
}
// success!
print(todo.description())
print(todo.title)
}
}
}
@voluntadpear
Copy link

I added a default implementation of ResponseJSONCollectionSerializable

extension ResponseJSONCollectionSerializable where Self: ResponseJSONObjectSerializable {
    static func collection(json: SwiftyJSON.JSON) -> [Self] {
        var items: [Self] = []

        if let jsonItems = json.array {
            for jsonItem in jsonItems {
                if let item = Self(json: jsonItem) {
                    items.append(item)
                }
            }
        }

        return items
    }
}

@hackolein
Copy link

I do same with the collection function, but XCODE says Protocol 'ResponseJSONCollectionSerializable' requirement 'collection' cannot be satisfied by a non-final class ('Todo') because it uses 'Self' in a non-parameter, non-result type position. Is there a other solution then make the class final?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment