Last active
December 4, 2018 04:52
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public protocol ResponseJSONObjectSerializable { | |
init?(json: SwiftyJSON.JSON) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} |
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
I added a default implementation of ResponseJSONCollectionSerializable