Ambigious reference to member in Swift 2 - swift

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

Related

Generic Service layer with different results in swift

I have prepared a Generic Service layer in my tutorial, where the responses differ from one API call to another and as i know generic is capable of handling this.
Yet i find it hard to call different APIs that has differnet responses.
For example the GetPosts --> Response[Array of Post Model]
For Example Delete POst --> Response (Post Model)
Knowing that both calls reach the below in the service layer
else if let jsonArray = json as? [Any] {
let object = Mapper<T>().mapArray( JSONObject: jsonArray)
completion(.success(object!))
}
You can check the response of each call on this Page
Here below is the Service LAYER CLASS this works fine when i call the getPosts API since in the saervice layer i defined the completiuon as Array --> [T], an in the getPosts Function i expect response as [PostsModel]
But when i try to call the DeletePost i face some complications since the result should be PostModel and not an array of PostModel.
When i modify the Servicelayer to accept T and not [T] i face some complications..
Hope anyone can help
import Foundation
import ObjectMapper
import Alamofire
class ServiceLayer {
class func request<T: Mappable>(router: Router, completion: #escaping (Result<[T], Error>) -> ()) {
do {
AF.request(try router.asURLRequest()).validate().responseData { (data) in
print(data)
let result = data.result
switch result {
case .success(let data):
do {
//Response as Dictionary
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? Any {
if let jsonDict = json as? [String : Any] {
let object = Mapper<T>().map(JSON: jsonDict)
completion(.success(object as! [T]))
}
//Response as Array
else if let jsonArray = json as? [Any] {
let object = Mapper<T>().mapArray( JSONObject: jsonArray)
completion(.success(object!))
}
//Response as String
else if let _ = String(data: data, encoding: .utf8){
}
}
} catch {
print(error.localizedDescription)
completion(.failure(error))
}
break
case .failure(let error):
print(error.localizedDescription)
completion(.failure(error))
break
}
}
} catch {
print(error.localizedDescription)
}
}
}
Here is the call to the APIs from the ViewModel
func getPosts() {
self.isLoading.value = true
ServiceLayer.request(router: .getPosts(userId: 1)) { (result : Result<[PostsModel], Error>) in
self.isLoading.value = false
switch result {
case .success(let baseModel):
self.array = baseModel
self.fetchPostsSucceded.value = true
case .failure(let error):
print(error.localizedDescription)
}
}
}
func deletePosts(postid: Int) {
self.isLoading.value = true
ServiceLayer.request(router: .deletePosts(id: postid)) { (result : Result<PostsModel, Error>) in
self.isLoading.value = false
switch result {
case .success(let baseModel):
print("")
case .failure(let error):
print(error.localizedDescription)
}
}
}
This is the PostModel Response, Knwoing that when i modify the Service layer completion to T and not [T] i get this
Class method 'request(router:completion:)' requires that '[PostsModel]' conform to 'Mappable'
import Foundation
import ObjectMapper
class PostsModel: Mappable, Codable {
var userId : Int?
var id : Int?
var title : String?
var body : String?
required init?(map: Map) {
}
func mapping(map: Map) {
userId <- map["userId"]
id <- map["id"]
title <- map["title"]
body <- map["body"]
}
}
Hope someone can help me resolve this, The needed result is to keep only one service layer that accepts any call and in the API calls to specify the return value.
if your service request<T: Mappable> get one object (not array) you decode it as array anyway
if let jsonDict = json as? [String : Any] {
let object = Mapper<T>().map(JSON: jsonDict)
completion(.success(object as! [T])) //<-- array anyway
}
but in function deletePosts you espect as Result<PostsModel, Error> not Result<[PostsModel], Error>. so if your service can answering only arrays you should change your
ServiceLayer.request(router: .deletePosts(id: postid)) { (result : Result<PostsModel, Error>) in
to
ServiceLayer.request(router: .deletePosts(id: postid)) { (result : Result<[PostsModel], Error>) in

Using Generics / Codable w/ API response 204 NO CONTENT

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!)
}

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)
}
}

Alamofire protocol return array of model objects

I'm using alamofire and it's serializing protocols. I have a model and its working great. Now, exactly how do I get an array of that model from these methods?
static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [DataObject] {
var daos: [DataObject] = []
if let representation = representation as? [[String: AnyObject]] {
for contentRepresentation in representation {
if let content = DataObject(response: response, representation: contentRepresentation) {
daos.append(content)
}
}
}
return daos
}
class func populateData() {
Alamofire.request(.GET, url)
.responseCollection { (response: Response<[DataObject], NSError>) in
//response.result.value how do i pass this to my viewcontroller?
}
}
Are you getting a JSON response from the request?
If so, you may be able to try something along the lines of this:
Alamofire.request(.GET, url).responseJSON {
response -> () in
switch response.result {
case .Success(let object):
if let urlResponse = response.response {
let dataObjects = collection(response: urlResponse, representation: object)
// Pass the objects back to your view controller here
}
break
case .Failure(let error):
print("Error: ", error)
break
}
}

Sorting JSON when using Alamofire and SwiftyJSON

Morning all,
I've been following along the examples in the excellent iOS Apps With REST APIs book and as a result using Alamofire and SwiftyJSON. One thing I don't see mentioned in the book is how you would sort the incoming json objects into a specific order, eg. date. The code I've pasted below works fine for pulling in the json objects however as far as I can tell, they're automatically ordered by created_by. I'd like to sort by a different order, lets say my class was called Vegetable and had a name attribute so that I could sort by something like:
.sort { $0.name < $1.name }
I'll start with the Vegetable class in Vegetable.swift
class Vegetable: ResponseJSONObjectSerializable {
var id: Int?
var name : String?
var date: NSDate?
}
Inside my JSONSerializer file I have the following, I'm not sure I'd wish to change the order directly in here as I'd prefer some more flexibility with each call.
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)
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: serializer, completionHandler: completionHandler)
}
Then, in my APIManager I have the following function
func getAllVegetables(completionHandler: (Result<[Vegetable], NSError>) -> Void) {
Alamofire.request(VegetableRouter.GetVegetables())
.responseArray { (response:Response<[Vegetable], NSError>) in
completionHandler(response.result)
}
}
Finally, populate my tableview I have:
func loadVegetables() {
self.isLoading = true
VegetablesAPIManager.sharedInstance.getAllVegetables() {
result in
self.isLoading = false
if self.refreshControl.refreshing {
self.refreshControl.endRefreshing()
}
guard result.error == nil else {
print(result.error)
// TODO: Display Error
return
}
if let fetchedVegetables = result.value {
self.vegetables = fetchedVegetables
for vegetable in fetchedVegetables {
// Nothing here at the moment
}
}
self.tableView.reloadData()
}
}
I appreciate any help I can get with this, Thanks!
Since you have a NSDate property, you can sort with the compare method of NSDate.
let sorted = result.value.sort { $0.date.compare($1.date) == .OrderedAscending }