Using Generics / Codable w/ API response 204 NO CONTENT - swift

I am using generics and codable with URLSession.
When I receive a response from an API, I check the status is in the 200 - 299 range and decode the data like so
guard let data = data, let value = try? JSONDecoder().decode(T.self, from: data) else {
return completion(.error("Could not decode JSON response"))
}
completion(.success(value))
This is then passed off to the completion handler and everything is OK.
I have a new endpoint I must POST too however, this endpoint returns a 204 with no content body.
As such, I cannot decode the response, simply as I cannot pass in a type?
My completion handler expects
enum Either<T> {
case success(T)
case error(String?)
}
and switching on my response statusCode like so
case 204:
let value = String(stringLiteral: "no content")
return completion(.success(value))
produces an error of
Member 'success' in 'Either<>' produces result of type 'Either',
but context expects 'Either<>'
My APIClient is
protocol APIClientProtocol: class {
var task: URLSessionDataTask { get set }
var session: SessionProtocol { get }
func call<T: Codable>(with request: URLRequest, completion: #escaping (Either<T>) -> Void) -> Void
func requestRefreshToken<T: Codable>(forRequest failedRequest: URLRequest, completion: #escaping (Either<T>) -> Void) -> Void
}
extension APIClientProtocol {
func call<T: Codable>(with request: URLRequest, completion: #escaping (Either<T>) -> Void) -> Void {
task = session.dataTask(with: request, completionHandler: { [weak self] data, response, error in
guard error == nil else {
return completion(.error("An unknown error occured with the remote service"))
}
guard let response = response as? HTTPURLResponse else {
return completion(.error("Invalid HTTPURLResponse recieved"))
}
switch response.statusCode {
case 100...299:
guard let data = data else { return completion(.error("No data in response")) }
guard let value = try? JSONDecoder().decode(T.self, from: data) else { return completion(.error("Could not decode the JSON response")) }
completion(.success(value))
case 300...399: return completion(.error(HTTPResponseStatus.redirection.rawValue))
case 400: return completion(.error(HTTPResponseStatus.clientError.rawValue))
case 401: self?.requestRefreshToken(forRequest: request, completion: completion)
case 402...499: return completion(.error(HTTPResponseStatus.clientError.rawValue))
case 500...599: return completion(.error(HTTPResponseStatus.serverError.rawValue))
default:
return completion(.error("Request failed with a status code of \(response.statusCode)"))
}
})
task.resume()
}
}

Make your Either enum success type optional
enum Either<T> {
case success(T?)
case error(String)
}
Create a case for a 204 response status, passing nil
case 204:
completion(.success(nil))
You can then use a typealias and create something generic like
typealias NoContentResponse = Either<Bool>
You should then be able to switch on NoContentResponse.

case 200...299: //success status code
if data.isEmpty {
let dummyData = "{}".data(using: .utf8)
//Use empty model
model = try JSONDecoder().decode(T, from: dummyData!)
}

Related

How Do I relogin after getting a 401 back using URLSession and CombineAPI

I'm new to combine and URLSession and I'm trying to find a way to log in after I get a 401 error back. My Set up for the URLSession.
APIErrors:
enum APIError: Error {
case requestFailed
case jsonConversionFailure
case invalidData
case responseUnsuccessful
case jsonParsingFailure
case authorizationFailed
var localizedDescription: String{
switch self{
case .requestFailed: return "Request Failed"
case .invalidData: return "Invalid Data"
case .responseUnsuccessful: return "Response Unsuccessful"
case .jsonParsingFailure: return "JSON Parsing Failure"
case .jsonConversionFailure: return "JSON Conversion Failure"
case .authorizationFailed: return "Failed to login the user."
}
}
}
The CombinAPI itself, I'm trying to catch the 401 either in .catch or .tryCatch, but proving not as easy as I thought.
//1- A Protocol that has an URLSession and a function that returns a publisher.
protocol CombineAPI{
var session: URLSession { get}
// var authenticationFeed: AuthenticationFeed { get }
func execute<T>(_ request: URLRequest, decodingType: T.Type, queue: DispatchQueue, retries: Int) -> AnyPublisher<T, Error> where T: Decodable
//func reauthenticate<T>(_ request: URLRequest, decodingType: T.Type, queue: DispatchQueue, retries: Int) -> AnyPublisher<T, Error> where T: Decodable
}
//2 - Extending CombineAPI so we can have a default implementation.
extension CombineAPI {
func authenticationFeed() -> URLRequest{
return AuthenticationFeed.login(parameters: UserCredentials(userName: UserSettings.sharedInstance.getEmail(), password: UserSettings.sharedInstance.getPassword())).request
}
func execute<T>(_ request: URLRequest,
decodingType: T.Type,
queue: DispatchQueue = .main,
retries: Int = 0) -> AnyPublisher<T, Error> where T: Decodable{
return session.dataTaskPublisher(for: request)
.tryMap {
guard let response = $0.response as? HTTPURLResponse, response.statusCode == 200 else{
let response = $0.response as? HTTPURLResponse
if response?.statusCode == 401{
throw APIError.authorizationFailed
}
print(response!.statusCode)
throw APIError.responseUnsuccessful
}
//Return the data if everything is good
return $0.data
}
.catch(){ _ in
//Try to relogin here or in tryCatch block
}
// .tryCatch { error in
// if Error as? APIError == .authorizationFailed {
// let subcription = self.callFunction().switchToLatest().flatMap { session.dataTaskPublisher(for: request)}.eraseToAnyPublisher()
// return subcription
// }else{
// throw APIError.responseUnsuccessful
// }
// }
.decode(type: T.self, decoder: JSONDecoder())
.receive(on: queue)
.retry(retries)
.eraseToAnyPublisher()
}
func reauthenticate<T>( decodingType: Token.Type, queue: DispatchQueue = .main,retries: Int = 2) -> AnyPublisher<T, Error> where T: Decodable{
return session.dataTaskPublisher(for: self.authenticationFeed())
.tryMap{
guard let response = $0.response as? HTTPURLResponse, response.statusCode == 200 else{
let response = $0.response as? HTTPURLResponse
if response?.statusCode == 401{
throw APIError.authorizationFailed
}
print(response!.statusCode)
throw APIError.responseUnsuccessful
}
//Return the data if everything is good
return $0.data
}
.decode(type: T.self, decoder: JSONDecoder())
.receive(on: queue)
.retry(retries)
.eraseToAnyPublisher()
}
}
This is the feed that will create the URL request itself:
enum UserFeed{
case getUser(userId: Int)
}
extension UserFeed: Endpoint{
var base: String {
return "http://192.168.1.15:8080"
}
var path: String {
switch self{
case .getUser(let userId): return "/api/v1/User/\(userId)"
}
}
var request: URLRequest{
let url = urlComponents.url!
var request = URLRequest(url: url)
switch self{
case .getUser(_):
request.httpMethod = CallType.get.rawValue
request.setValue("*/*", forHTTPHeaderField: "accept")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue(token,forHTTPHeaderField: "tokenheader")
print(token)
return request
}
}
}
Then the client itself where you would create this would be in your viewModel, so you can make the web request for that type of data:
import Foundation
import Combine
final class UserClient: CombineAPI{
var authenticate = PassthroughSubject<Token, Error>()
var session: URLSession
init(configuration: URLSessionConfiguration){
self.session = URLSession(configuration: configuration)
}
convenience init(){
self.init(configuration: .default)
}
func getFeedUser(_ feedKind: UserFeed) -> AnyPublisher<User, Error>{
return execute(feedKind.request, decodingType: User.self, retries: 2)
}
}
I keep trying to make a new request to my authenticationClient, but it returns a different data type, so the ComineAPI doesn't like it. I'm not sure what I should do, otherwise, it works great until I have to authenticate, or get a new token? Any help would be appreciated, thanks.
I Just need it to log in, so I can save the new token to user settings and then continue on the request it left off with, If I can't get a new token, then I return an error to have the user login.
So if I understood correctly you want to be able to catch error 401 and send a different API request from the one you were previously using.
In that case you want to perform the following just as you wrote:
func execute<T>(_ request: URLRequest,
decodingType: T.Type,
queue: DispatchQueue = .main,
retries: Int = 0) -> AnyPublisher<T, APIError> where T: Decodable{
return session.dataTaskPublisher(for: request)
.mapError { error in
if error.statusCode == 401{
return APIError.authorizationFailed
}
return APIError.someOtherGenericError
}
.map{$0}
.decode(type: T.self, decoder: JSONDecoder())
.catch(){ error in
if error == .authorizationFailed {
return session.dataTaskPublisher(for: request) // a new URLRequest that will call for the token generation.
}
}
.receive(on: queue)
.eraseToAnyPublisher()
}
Now you can use the function catch, you need to return a publisher with the same value as you provided, meaning if you defined that you want a "User" object to be returned, then the catch new publisher will have to return the same type of object.
If you want to make it more generic, you can either handle the .catch request for generating token in the .sink closure, or maybe create a function specific for signing in and not using the generic one.
Sorry for giving so little choices, these solutions are the only things that came up from the top of my head.
Hope it helps.

Alamofire - How to get API error from AFError

In my quest to implement Alamofire 5 correctly and handle custom error model responses, I have yet to find an accepted answer that has an example.
To be as thorough as possible, here is my apiclient
class APIClient {
static let sessionManager: Session = {
let configuration = URLSessionConfiguration.af.default
configuration.timeoutIntervalForRequest = 30
configuration.waitsForConnectivity = true
return Session(configuration: configuration, eventMonitors: [APILogger()])
}()
#discardableResult
private static func performRequest<T:Decodable>(route:APIRouter, decoder: JSONDecoder = JSONDecoder(), completion:#escaping (Result<T, AFError>)->Void) -> DataRequest {
return sessionManager.request(route)
// .validate(statusCode: 200..<300) // This will kill the server side error response...
.responseDecodable (decoder: decoder){ (response: DataResponse<T, AFError>) in
completion(response.result)
}
}
static func login(username: String, password: String, completion:#escaping (Result<User, AFError>)->Void) {
performRequest(route: APIRouter.login(username: username, password: password), completion: completion)
}
}
I am using it like this
APIClient.login(username: "", password: "") { result in
debugPrint(result)
switch result {
case .success(let user):
debugPrint("__________SUCCESS__________")
case .failure(let error):
debugPrint("__________FAILURE__________")
debugPrint(error.localizedDescription)
}
}
I have noticed that if I use .validate() that the calling function will receive a failure however the response data is missing. Looking around it was noted here and here to cast underlyingError but thats nil.
The server responds with a parsable error model that I need at the calling function level. It would be far more pleasant to deserialize the JSON at the apiclient level and return it back to the calling function as a failure.
{
"errorObject": {
"summary": "",
"details": [{
...
}]
}
}
UPDATE
Thanks to #GIJoeCodes comment I implemented this similar solution using the router.
class APIClient {
static let sessionManager: Session = {
let configuration = URLSessionConfiguration.af.default
configuration.timeoutIntervalForRequest = 30
configuration.waitsForConnectivity = true
return Session(configuration: configuration, eventMonitors: [APILogger()])
}()
#discardableResult
private static func performRequest<T:Decodable>(route:APIRouter, decoder: JSONDecoder = JSONDecoder(), completion:#escaping (_ response: T?, _ error: Error?)->Void) {
sessionManager.request(route)
.validate(statusCode: 200..<300) // This will kill the server side error response...
.validate(contentType: ["application/json"])
.responseJSON { response in
guard let data = response.data else { return }
do {
switch response.result {
case .success:
let object = try decoder.decode(T.self, from: data)
completion(object, nil)
case .failure:
let error = try decoder.decode(ErrorWrapper.self, from: data)
completion(nil, error.error)
}
} catch {
debugPrint(error)
}
}
}
// MARK: - Authentication
static func login(username: String, password: String, completion:#escaping (_ response: User?, _ error: Error?)->Void) {
performRequest(route: APIRouter.login(username: username, password: password), completion: completion)
}
}
Called like this
APIClient.login(username: "", password: "") { (user, error) in
if let error = error {
debugPrint("__________FAILURE__________")
debugPrint(error)
return
}
if let user = user {
debugPrint("__________SUCCESS__________")
debugPrint(user)
}
}
This is how I get the errors and customize my error messages. In the validation, I get the errors outside of the 200..<300 response:
AF.request(
url,
method: .post,
parameters: json,
encoder: JSONParameterEncoder.prettyPrinted,
headers: headers
).validate(statusCode: 200..<300)
.validate(contentType: ["application/json"])
.responseJSON { response in
switch response.result {
case .success(let result):
let json = JSON(result)
onSuccess()
case .failure(let error):
guard let data = response.data else { return }
do {
let json = try JSON(data: data)
let message = json["message"]
onError(message.rawValue as! String)
} catch {
print(error)
}
onError(error.localizedDescription)
}
debugPrint(response)
}
First, there's no need to use responseJSON if you already have a Decodable model. You're doing unnecessary work by decoding the response data multiple times. Use responseDecodable and provide your Decodable type, in this case your generic T. responseDecodable(of: T).
Second, wrapping your expected Decodable types in an enum is a typical approach to solving this problem. For instance:
enum APIResponse<T: Decodable> {
case success(T)
case failure(APIError)
}
Then implement APIResponse's Decodable to try to parse either the successful type or APIError (there are a lot of examples of this). You can then parse your response using responseDecodable(of: APIResponse<T>.self).

Generic Decoder for Swift using a protocol

I tried to use a generic Json Decoder for all of my models using a protrocol.
//Here the definition of the protocol:
func fetch<T: Decodable>(with request: URLRequest, decode: #escaping (Decodable) -> T?, completion: #escaping (Result<T, APIError>) -> Void) {.. other Code}
//Here the implementation:
func getData(from endPoint: Endpoint, completion: #escaping (Result<ApiResponseArray<Codable>, APIError>) -> Void) {
let request = endPoint.request
fetch(with: request, decode: { json -> Decodable in
guard let dataResult = json as? modelData else { return nil }
return dataResult
}, completion: completion)
}
ApiResponseArray gives me the error: Protocol type 'Codable' (aka 'Decodable & Encodable') cannot conform to 'Decodable' because only concrete types can conform to protocols. But how can I implement a generic decoder and passing them different models. I think I have to modify my protocol definition but how? I would like to pass the model and then receive the decoded data for the model (in my example modelData). Its obvious that the program runs when I write:
func getData(from endPoint: Endpoint, completion: #escaping (Result, APIError>) I mean when I use the concrete Model, but I want to pass the model, so that I can use the class for different models.
Thanks,
Arnold
A protocol cannot conform to itself, Codable must be a concrete type or can only be used as a generic constraint.
In your context you have to do the latter, something like this
func fetch<T: Decodable>(with request: URLRequest, decode: #escaping (Data) throws -> T, completion: #escaping (Result<T, APIError>) -> Void) { }
func getData<T: Decodable>(_ : T.Type = T.self, from endPoint: Endpoint, completion: #escaping (Result<T, APIError>) -> Void) {
let request = endPoint.request
fetch(with: request, decode: { data -> T in
return try JSONDecoder().decode(T.self, from: data)
}, completion: completion)
}
A network request usually returns Data which is more reasonable as parameter type of the decode closure
I can suggest to you how to use Decodable with your API call structure by using Alamofire.
I have created RequestManager class which inherits from SessionManager and added request call inside which common to all.
class RequestManager: SessionManager {
// Create shared instance
static let shared = RequestManager()
// Create http headers
lazy var httpHeaders : HTTPHeaders = {
var httpHeader = HTTPHeaders()
httpHeader["Content-Type"] = "application/json"
httpHeader["Accept"] = "application/json"
return httpHeader
}()
//------------------------------------------------------------------------------
// MARK:-
// MARK:- Request Methods
//------------------------------------------------------------------------------
func responseRequest(_ url: String, method: Alamofire.HTTPMethod, parameter: Parameters? = nil, encoding: ParameterEncoding, header: HTTPHeaders? = nil, completionHandler: #escaping (DefaultDataResponse) -> Void) -> Void {
self.request(url, method: method, parameters: parameter, encoding: encoding, headers: header).response { response in
completionHandler(response)
}
}
}
Then after one more class created NetworkManager class which hold required get/post method call and decode json by JSONDecoder as follow:
class NetworkManager {
static let shared = NetworkManager()
var progressVC : ProgressVC?
//----------------------------------------------------------------
// MARK:-
// MARK:- Get Request Method
//----------------------------------------------------------------
func getResponse<T: Decodable>(_ url: String, parameter: Parameters? = nil, encoding: ParameterEncoding = URLEncoding.default, header: HTTPHeaders? = nil, showHUD: HUDFlag = .show, message: String? = "Please wait...", decodingType: T.Type, completion: #escaping (Decodable?, APIError?) -> Void) {
DispatchQueue.main.async {
self.showHideHud(showHUD: showHUD, message: "")
}
RequestManager.shared.responseRequest(url, method: .get, parameter: parameter, encoding: encoding, header: header) { response in
DispatchQueue.main.async {
self.showHideHud(showHUD: .hide, message: "")
}
guard let httpResponse = response.response else {
completion(nil, .requestFailed("Request Failed"))
return
}
if httpResponse.statusCode == 200 {
if let data = response.data {
do {
let genericModel = try JSONDecoder().decode(decodingType, from: data)
completion(genericModel, nil)
} catch {
do {
let error = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any]
if let message = error!["message"] as? String {
completion(nil, .errorMessage(message)!)
} else if let message = error!["message"] as? Int {
completion(nil, .errorMessage(String(describing: "Bad Request = \(message)")))
}
} catch {
completion(nil, .jsonConversionFailure("JSON Conversion Failure"))
}
}
} else {
completion(nil, .invalidData("Invalid Data"))
}
} else {
completion(nil, .responseUnsuccessful("Response Unsuccessful"))
}
}
}
}
ProgressVC is my custom class to show progress view when api call.
After that, I have created DataManager class which will help me to create request url.
class DataManager: NSObject {
//------------------------------------------------------------------------------
// MARK:- Variables
//------------------------------------------------------------------------------
static let shared = DataManager()
let baseUrl = WebServiceURL.local
//------------------------------------------------------------------------------
// MARK:- Custom Methods
//------------------------------------------------------------------------------
// Get API url with endpoints
func getURL(_ endpoint: WSEndPoints) -> String {
return baseUrl + endpoint.rawValue
}
}
I have created following enum to send data or error in my completion block.
enum Result<T, U> where U: Error {
case success(T)
case failure(U)
}
Here is list of error which stored custom message related to status fired during api call.
enum APIError: Error {
case errorMessage(String)
case requestFailed(String)
case jsonConversionFailure(String)
case invalidData(String)
case responseUnsuccessful(String)
case jsonParsingFailure(String)
var localizedDescription: String {
switch self {
case.errorMessage(let msg):
return msg
case .requestFailed(let msg):
return msg
case .jsonConversionFailure(let msg):
return msg
case .invalidData(let msg):
return msg
case .responseUnsuccessful(let msg):
return msg
case .jsonParsingFailure(let msg):
return msg
}
}
}
Then after, I will extend this DataManager class to call web service based on module. So I will create Swift file and extend DataManager class and call relative API.
See following, In API call I will return relative model into Result like Result<StoreListModel, APIError>
extension DataManager {
// MARK:- Store List
func getStoreList(completion: #escaping (Result<StoreListModel, APIError>) -> Void) {
NetworkManager.shared.getResponse(getURL(.storeList), parameter: nil, encoding: JSONEncoding.default, header: getHeaders("bd_suvlascentralpos"), showHUD: .show, message: "Please wait...", decodingType: StoreListModel.self) { (decodableData, apiError) in
if apiError != nil {
completion(.failure(apiError!))
} else {
guard let userData = decodableData as? StoreListModel else {
completion(.failure(apiError!))
return
}
completion(.success(userData))
}
}
}
}
From completion block of request I will get decodable data which here safely type cast.
Use:
DataManager.shared.getStoreList { (result) in
switch result {
case .success(let storeListModel):
if let storeList = storeListModel, storeList.count > 0 {
self.arrStoreList = storeList
self.tblStoreList.isHidden = false
self.labelEmptyData.isHidden = true
self.tblStoreList.reloadData()
} else {
self.tblStoreList.isHidden = true
self.labelEmptyData.isHidden = false
}
break
case .failure(let error):
print(error.localizedDescription)
break
}
}
Note:- Some variables, models classes are my custom. You can replace it with your.

Alamofire, handle void call that can return response with httpError and json body, in Swift 3

For automatic response object serialization I'm using example (with some modifications) provided on Alamofire's GitHub page. It works fine for non Void calls. But my Backend API has calls that return just httpStatus code with no response body if it was successful call or with JSON in body if there was as exception on the server side. I was thinking about another extension for DataRequest that will return success(with no data) if httpStatusCode == 200 and return failure with custom error object serialized from JSON for other statusCodes. But I have no idea how to return success with no data. I will really appreciate any help.
extension DataRequest {
func responseObject<T: ResponseObjectSerializable>(queue: DispatchQueue? = nil, completionHandler: #escaping (DataResponse<T>) -> Void) -> Self
{
let responseSerializer = DataResponseSerializer<T> { request, response, data, error in
guard error == nil else { return .failure(BackendError.network(error: error!)) }
let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
let result = jsonResponseSerializer.serializeResponse(request, response, data, nil)
guard case let .success(jsonObject) = result else {
return .failure(BackendError.jsonSerialization(error: result.error!))
}
if response!.statusCode != 200 {
if let jsonWithException = jsonObject as? [String:Any]{
return .failure(BackendError.serverException(message: "serverException, statusCode=\(response!.statusCode)", respomseBody: jsonWithException))
}else{
return .failure(BackendError.serverException(message: "unparseble serverException, statusCode != 200", respomseBody: nil))
}
}
guard let _ = response, let responseObject = T(representation: jsonObject) else {
return .failure(BackendError.objectSerialization(reason: "JSON could not be serialized: \(jsonObject)"))
}
return .success(responseObject)
}
return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}

Ambigious reference to member in Swift 2

I want to create a generic Network Operation class to handle the communication to my server. I tested a few things and the error must be the responseArrayfunction. But I don't know where?
class NetworkOperation<T> {
let requestType: NSMutableURLRequest
var arrayItems: Array<T>?
typealias JSONDictionaryCompletion = ([String:AnyObject]?) -> Void
typealias ObjectArrayCompletion = (Response<[T], NSError>) -> Void
init(requestType: NSMutableURLRequest, arrayItems: Array<T>) {
self.requestType = requestType
self.arrayItems = arrayItems
}
func downloadJSONFromURL(completion: JSONDictionaryCompletion) {
}
func downloadObjectsFromURL(completion: ObjectArrayCompletion) {
Alamofire.request(requestType).responseArray { (response) in
if let httpResponse = response, let statusCode = httpResponse.response {
switch(statusCode) {
case 200:
print("OK")
}
}
}
}
}
extension Alamofire.Request {
// responseObject<...>(...) declares a new .responseX handling function on Alamofire.Request. It uses the responseSerializer as a custom serializer.
// The <T> means this is a genertic method: it can work with different types of objects. These types must implement the ResponseJSONObjectSerializable protocol.
// This is needed to guarantee that any type of object that will be passed in will have an init function that takes JSON. The response function takes a single
// argument called completionHandler. This is the method being called when done parsing the JSON and creating the object to be called async.
// The completion handler has a single argument Response<T, NSError>. The response object packs up the Result along iwth all the other info from the transaction.
// The responseObject function returns an Alamofire.Request object
public func responseObject<T: ResponseJSONObjectSerializable>(completionHandler: Response<T, NSError> -> Void) -> Self {
// Create the response serilaizer that will work with the generic T type and an NSError. It will take in the results of the URL Request (request, response, data, error)
// and use the Result type defined by Alamofire to return success with the object or failure with an error. The responseObject just returns the responseserializer that gets created
// and allows passing the completion handler where it needs to go.
let serializer = ResponseSerializer<T, NSError> { (request, response, data, error) in
// Checks that it has valid data using guard. Then it turns the data into JSON and parses it using SwiftyJSON.
// Then it creates a new copy of the type of class.
guard error == nil else {
return .Failure(error!)
}
guard let responseData = data else {
let failureReason = "Object 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)
// Get Data content of JSON
let jsonData = json["data"]
if let object = T(json: jsonData) {
return .Success(object)
} else {
let failureReason = "Object could not be created from JSON."
let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: serializer, completionHandler: completionHandler)
}
// Iterate through the elements in the json and create object out of each one and add it to the objects array.
public func responseArray<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 = "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)
// Get Data content of JSON
let jsonData = json["data"]
var objects: [T] = []
for (_, item) in jsonData {
if let object = T(json: item) {
objects.append(object)
}
}
return .Success(objects)
case .Failure(let error):
return .Failure(error)
}
}
return response(responseSerializer: serializer, completionHandler: completionHandler)
}
}
But when I want to check the status code in the downloadObjectsFromURL function, I get the compiler error message:
Ambigious Reference to member 'request(::parameters:encoding:headers:)'
Where does this error come from. Did I unwrap the optionals in a wrong way?
UPDATE
I tested this:
func createObjectsFromJSON(completion: ObjectArrayCompletion) {
Alamofire.request(requestType).responseArray { (response: Response<[AED], NSError>) -> Void in
print("OK")
}
}
AED is a custom class I created. No error message anymore. When I switch AED to T this error pops up
func createObjectsFromJSON(completion: ObjectArrayCompletion) {
Alamofire.request(requestType).responseArray { (response: Response<[T], NSError>) -> Void in
print("OK")
}
}
expected argument type Response<[_], NSError> -> Void