I use Alamofire-beta3, I want to custom response serialization, but I get a error "Cannot call value of non-function type 'NSHTTPURLResponse?' "
https://cloud.githubusercontent.com/assets/7556575/10444657/b0d691fc-719b-11e5-878e-bdd46d03be3b.png
My code is below:
public static func objectSerializer <T: MTLModel> () -> ResponseSerializer <T, MTLError> {
return ResponseSerializer { request, response, data, error in
// Http Error with Http status code
guard error == nil else {
let failureReason = "Network Error"
let error = MTLError.errorWithCode(error!.code, failureReason: failureReason)
return .Failure(error)
}
// data be null
guard let validData = data where validData.length > 0 else {
let failureReason = "JSON could not be serialized. Response data was nil or zero length."
let error = MTLError.errorWithCode(.ResponseDataNull, failureReason: failureReason)
return .Failure(error)
}
do {
let JSON = try NSJSONSerialization.JSONObjectWithData(validData, options: .AllowFragments)
// response data is not a obejct json
guard let json = JSON as? [String : AnyObject] else {
let failureReason = "JSON could not be serialized. Response data is not a obejct JSON"
let error = MTLError.errorWithCode(.JSONSerializeToObjectFailed, failureReason: failureReason)
return .Failure(error)
}
// Http request successful(state 200 OK), but response data not include `Error Section`
guard let errorDict = json["error"] as? [String : AnyObject] else {
let failureReason = "JSON could not be serialized. Http request successful(state 200 OK), but response data not include `Error Section`"
let error = MTLError.errorWithCode(.JSONSerializeErrorSectionFailed, failureReason: failureReason)
return .Failure(error)
}
// mean request failed
if errorDict.count != 0 {
let error: MTLError!
do {
error = try MTLJSONAdapter.modelOfClass(MTLError.self, fromJSONDictionary: errorDict) as! MTLError
} catch _ {
let failureReason = "MTLError object serialize failed"
error = MTLError.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
}
return .Failure(error)
} else {
// mean request successful
let dataDict = json["data"] as? [String : AnyObject]
do {
let object = try MTLJSONAdapter.modelOfClass(T.self, fromJSONDictionary: dataDict) as! T
return .Success(object)
} catch _ {
let failureReason = "\(T.self) object serialize failed"
let error = MTLError.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
}
} catch _ {
let failureReason = "Network Error"
let error = MTLError.errorWithCode(error!.code, failureReason: failureReason)
return .Failure(error)
}
}
}
public func responseObject<T: MTLModel> (queue: dispatch_queue_t? = nil, willStart: (() -> Void)? = nil, didStop: (() -> Void)? = nil, completionHandler: Response<T, NSError> -> Void) -> Self
{
willStart?()
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
return response(responseSerializer: Request.objectSerializer(), completionHandler: { (response: Response<T, NSError>) in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { () -> Void in
self.printResponse(response)
dispatch_async(queue ?? dispatch_get_main_queue()) {
didStop?()
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
completionHandler(response)
}
})
})
}
I have see https://github.com/Alamofire/Alamofire/issues/817 , but I still don't know how to change my code. Please help me.
Related
I am retrieving details from my api with POST method, I have included the parameters here. I am able to print the response in console., but to the view it's quite complicating. Here added the code which i've tried. I appreciate if someone help me to get this done.
My network code goes here:
class HostApi: ObservableObject {
#Published var todos = [HostsHome]()
#Published var amenity = [AmenitiesHome]()
func loadData() {
let Url = String(format: Host_home)
guard let serviceUrl = URL(string: Url) else {
return
}
let parameters: [String : Any] = [
"request" : ["email" : "xxxxxxxxxxx.com",
"starting" : 0,
"ending" : 10]
]
var request = URLRequest(url: serviceUrl)
request.httpMethod = "POST"
request.setValue("Application/json", forHTTPHeaderField: "Content-Type")
guard let httpBody = try? JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) else {
return
}
request.httpBody = httpBody
request.timeoutInterval = 20
let session = URLSession.shared
session.dataTask(with: request) { data, response, error in
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: .json5Allowed)
print(json)
} catch {
print(error)
}
}
}.resume()
}
}
ContentView code goes here :
struct Hosts_Home: View {
#StateObject var viewModel = HostApi()
var body: some View {
ForEach(viewModel.todos, id: \.title) { todo in
Text(todo.title!)
}
.onAppear {
viewModel.loadData()
}
}
}
[![api[![parameters][1]][1]][2]
[1]: https://i.stack.imgur.com/R5sBV.png
[2]: https://i.stack.imgur.com/yMj0t.png
Found a solution, I changed my network class like this below and it worked.
class HostApi: ObservableObject {
#Published var todos = [HostsHome]()
#Published var amenity = [AmenitiesHome]()
func loadData() {
let url = URL(string: Host_home)
guard let requestUrl = url else { fatalError() }
var request = URLRequest(url: requestUrl)
request.httpMethod = "POST"
let postString = "email=xxxxxxx#gmail.com&starting=0&ending=10";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
do {
if let todoData = data {
let decodedData = try JSONDecoder().decode([HostsHome].self, from: todoData)
DispatchQueue.main.async {
self.todos = decodedData
self.amenity = decodedData[0].amenities
print(decodedData[0].propertyTypeGroup)
}
} else {
print("No data")
}
} catch {
print(error)
}
}
task.resume()
}
}
i have the following code in which i'm trying to download exchange rates into my app to use in currency conversion.
The data fetch seems to work ok, as does the json decoding model, but i'm unable to get the data through the Rates variable
import SwiftUI
struct ExchangeRates: Codable {
var conversionRates: [String: Double]?
init(conversionRates:[String:Double]) {
self.conversionRates = conversionRates
}
enum CodingKeys: String, CodingKey {
case conversionRates = "conversion_rates"
}
}
class DownloadingData:ObservableObject{
#Published var Rates:ExchangeRates = ExchangeRates.init(conversionRates: ["test" : 0])
init() {
datatask()
}
func datatask() {
guard let url = URL(string: "https://v6.exchangerate-api.com/v6/********************/latest/GBP") else {return}
URLSession.shared.dataTask(with: url){(data,response,error) in
guard let data = data else {
print("no data")
return
}
guard error == nil else {
print("error :\(String(describing: error))")
return
}
guard let response = response as? HTTPURLResponse else {
print("invalid response")
return
}
guard response.statusCode >= 200 && response.statusCode < 300 else {
print("status code should be 2xx, but is \(response.statusCode)")
return
}
guard let rates = try? JSONDecoder().decode(ExchangeRates.self, from: data) else {return}
print(rates) **// this works and prints out data**
DispatchQueue.main.async {
[weak self] in
self?.Rates = rates
print(self?.Rates) **// this works and prints out the data**
}
print(self.Rates) **// this doesnt print out anything**
}.resume()
print(Rates) **// this doesnt print out anything**
}
}
i can't seem to get the data into the Rates Variable
any guidance please
thanks
here is a sample of the console output :
ExchangeRates(conversionRates: Optional(["test": 0.0]))
Optional(test3.ExchangeRates(conversionRates: Optional(["BZD": 2.7002, "PHP": 68.7948, "PGK": 4.725, "BND": 1.8176, "HNL": 32.8885, "TND": 3.7553, "BDT": 115.2218, "SBD": 10.6866, "NIO": 47.4824, "XDR": 0.963, "IDR": 19213.9064, "XCD": 3.6453, "CAD": 1.7152, "UGX": 4778.6135,])
you could try this, using your ExchangeRates struct:
class DownloadingData: ObservableObject{
#Published var rates = ExchangeRates(conversionRates: ["test" : 0])
init() {
datatask()
}
func datatask() {
guard let url = URL(string: "https://v6.exchangerate-api.com/v6/********************/latest/GBP") else {return}
URLSession.shared.dataTask(with: url){(data,response,error) in
guard let data = data else {
print("no data")
return
}
guard error == nil else {
print("error :\(error)")
return
}
guard let response = response as? HTTPURLResponse else {
print("invalid response")
return
}
guard response.statusCode >= 200 && response.statusCode < 300 else {
print("status code should be 2xx, but is \(response.statusCode)")
return
}
guard let exRates = try? JSONDecoder().decode(ExchangeRates.self, from: data) else {return}
print(exRates)
DispatchQueue.main.async {
self.rates = exRates // <--- here
print(self.rates) // <--- here
}
// print(self.rates) // <--- NEVER here
}.resume()
}
}
EDIT-1: with completion closure:
class DownloadingData: ObservableObject{
#Published var rates = ExchangeRates(conversionRates: ["test" : 0])
init() {
datatask() { isDone in
print(self.rates) // <--- here OK
}
}
func datatask(completion: #escaping(Bool) -> ()) { // <--- here
guard let url = URL(string: "https://v6.exchangerate-api.com/v6/********************/latest/GBP") else {return}
URLSession.shared.dataTask(with: url){(data,response,error) in
guard let data = data else {
print("no data")
return
}
guard error == nil else {
print("error :\(error)")
return
}
guard let response = response as? HTTPURLResponse else {
print("invalid response")
return
}
guard response.statusCode >= 200 && response.statusCode < 300 else {
print("status code should be 2xx, but is \(response.statusCode)")
return
}
guard let exRates = try? JSONDecoder().decode(ExchangeRates.self, from: data) else {return}
print(exRates) // <--- here OK
DispatchQueue.main.async {
self.rates = exRates
print(self.rates) // <--- here OK
completion(true) // <--- here return completion
}
}.resume()
}
}
Declare rates – please with starting lowercase letter – as empty dictionary
#Published var rates = [String:Double]()
In the struct delete the init method and declare the dictionary also non-optional
struct ExchangeRates: Decodable {
let conversionRates: [String: Double]
enum CodingKeys: String, CodingKey {
case conversionRates = "conversion_rates"
}
}
in the DispatchQueue closure assign the value of conversionRates to rates
DispatchQueue.main.async { // no weak self needed
self.rates = rates.conversionRates
}
In the view enumerate the dictionary rates
I have some difficulty using Combine in SwiftUI with making an API request and then decoding the data and returning it. When calling the API Service, it states in the 'AnyPublisher<UserLoginResponse, APIError>' that the result will be of such type. However, I would want to reuse the API Service and decode the response to different model structures. How can I call the API Service while defining which data structure it has to decode the returned data to? For example, in another ViewModel I would want to decode the API data to a 'NewsUpdatesResponse' instead of 'UserLoginResponse'. The code I have now is as follows:
Most code comes from: tundsdev
API Service
struct APIService {
func request(from endpoint: APIRequest, body: String) -> AnyPublisher<UserLoginResponse, APIError> {
var request = endpoint.urlRequest
request.httpMethod = endpoint.method
if endpoint.authenticated == true {
request.setValue("testToken", forHTTPHeaderField: "token")
}
if body != "" {
let finalBody = body.data(using: .utf8)
request.httpBody = finalBody
}
return URLSession
.shared
.dataTaskPublisher(for: request)
.receive(on: DispatchQueue.main)
.mapError { _ in APIError.unknown}
.flatMap { data, response -> AnyPublisher<UserLoginResponse, APIError> in
guard let response = response as? HTTPURLResponse else {
return Fail(error: APIError.unknown).eraseToAnyPublisher()
}
print(response.statusCode)
if response.statusCode == 200 {
let jsonDecoder = JSONDecoder()
return Just(data)
.decode(type: UserLoginResponse.self, decoder: jsonDecoder)
.mapError { _ in APIError.decodingError }
.eraseToAnyPublisher()
}
else {
return Fail(error: APIError.errorCode(response.statusCode)).eraseToAnyPublisher()
}
}
.eraseToAnyPublisher()
}
}
Login ViewModel
class LoginViewModel: ObservableObject {
#Published var loginState: ResultState = .loading
private var cancellables = Set<AnyCancellable>()
private let service: APIService
init(service: APIService) {
self.service = service
}
func login(username: String, password: String) {
self.loginState = .loading
let cancellable = service
.request(from: .login, body: "username=admin&password=admin")
.sink { res in
print(res)
switch res {
case .finished:
self.loginState = .success
case .failure(let error):
self.loginState = .failed(error: error)
}
} receiveValue: { response in
print(response)
}
self.cancellables.insert(cancellable)
}
}
the following is untested, but you could try something along this line, using generic Decodable:
struct APIService {
func request<T: Decodable>(from endpoint: APIRequest, body: String) -> AnyPublisher<T, APIError> {
var request = endpoint.urlRequest
request.httpMethod = endpoint.method
if endpoint.authenticated == true {
request.setValue("testToken", forHTTPHeaderField: "token")
}
if body != "" {
let finalBody = body.data(using: .utf8)
request.httpBody = finalBody
}
return URLSession
.shared
.dataTaskPublisher(for: request)
.receive(on: DispatchQueue.main)
.mapError { _ in APIError.unknown}
.flatMap { data, response -> AnyPublisher<T, APIError> in // <-- here
guard let response = response as? HTTPURLResponse else {
return Fail(error: APIError.unknown).eraseToAnyPublisher()
}
print(response.statusCode)
if response.statusCode == 200 {
let jsonDecoder = JSONDecoder()
return Just(data)
.decode(type: T.self, decoder: jsonDecoder) // <-- here
.mapError { _ in APIError.decodingError }
.eraseToAnyPublisher()
}
else {
return Fail(error: APIError.errorCode(response.statusCode)).eraseToAnyPublisher()
}
}
.eraseToAnyPublisher()
}
}
you may also want to return an array of such Decodable:
func requestThem<T: Decodable>(from endpoint: APIRequest, body: String) -> AnyPublisher<[T], APIError> {
....
.flatMap { data, response -> AnyPublisher<[T], APIError> in
...
.decode(type: [T].self, decoder: jsonDecoder)
...
The final solution which worked for me was the following with the help of workingdog.
API Service
struct APIService {
func request<T: Decodable>(ofType type: T.Type, from endpoint: APIRequest, body: String) -> AnyPublisher<T, Error> {
var request = endpoint.urlRequest
request.httpMethod = endpoint.method
if endpoint.authenticated == true {
request.setValue("testToken", forHTTPHeaderField: "token")
}
if body != "" {
let finalBody = body.data(using: .utf8)
request.httpBody = finalBody
}
return URLSession
.shared
.dataTaskPublisher(for: request)
.receive(on: DispatchQueue.main)
.mapError { _ in Error.unknown}
.flatMap { data, response -> AnyPublisher<T, Error> in
guard let response = response as? HTTPURLResponse else {
return Fail(error: Error.unknown).eraseToAnyPublisher()
}
print(response.statusCode)
let jsonDecoder = JSONDecoder()
if response.statusCode == 200 {
return Just(data)
.decode(type: T.self, decoder: jsonDecoder)
.mapError { _ in Error.decodingError }
.eraseToAnyPublisher()
}
else {
do {
let errorMessage = try jsonDecoder.decode(APIErrorMessage.self, from: data)
return Fail(error: Error.errorCode(statusCode: response.statusCode, errorMessage: errorMessage.error ?? "Er is iets foutgegaan")).eraseToAnyPublisher()
}
catch {
return Fail(error: Error.decodingError).eraseToAnyPublisher()
}
}
}
.eraseToAnyPublisher()
}
}
Login ViewModel
class LoginViewModel: ObservableObject {
#Published var loginState: ResultState = .loading
private var cancellables = Set<AnyCancellable>()
private let service: APIService
init(service: APIService) {
self.service = service
}
func login(username: String, password: String) {
self.loginState = .loading
let preparedBody = APIPrepper.prepBody(parametersDict: ["username": username, "password": password])
let cancellable = service.request(ofType: UserLoginResponse.self, from: .login, body: preparedBody).sink { res in
switch res {
case .finished:
self.loginState = .success
print(self.loginState)
case .failure(let error):
self.loginState = .failed(stateIdentifier: error.statusCode, errorMessage: error.errorMessage)
print(self.loginState)
}
} receiveValue: { response in
print(response)
}
self.cancellables.insert(cancellable)
}
}
Note that I have made some minor changes to the passing of the username and password parameters in the meantime.
I am trying to update some code for a Alamofire custom response serializer I found this bit of code on "bits of cocoa".
extension Alamofire.Request {
public func responseCollection<T: Decodable>(completionHandler: Response<[T], NSError> -> Void) -> Self {
let responseSerializer = ResponseSerializer<[T], NSError> { request, response, data, error in
guard error == nil else { return .Failure(error!) }
let result = Alamofire
.Request
.JSONResponseSerializer(options: .AllowFragments)
.serializeResponse(request, response, data, error)
switch result {
case .Success(let value):
do {
return .Success(try [T].decode(value))
} catch {
return .Failure(Error.errorWithCode(.JSONSerializationFailed,
failureReason: "JSON parsing error, JSON: \(value)"))
}
case .Failure(let error): return.Failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
This is pre swift 3, and Response<[T], NSError> is now a single value specialization Response<[T]> because of this I am not sure how this extension would translate for the changes to Alamofire on the swift 3
I started to update this code this is as far as I got
extension Alamofire.Request {
public func responseCollection<T: Decodable>(completionHandler: (Response<[T]>) -> Void) -> Self {
let responseSerializer = ResponseSerializer<[T]> { request, response, data, error in
guard error == nil else { return .failure(error!) }
let result = Alamofire
.Request
.JSONResponseSerializer(options: .allowFragments)
.serializeResponse(request, response, data, error)
switch result {
case .success(let value):
do {
return .success(try [T].decode(value))
} catch {
return .failure(Error(.errorWithCode(.JSONSerializationFailed, failureReason: "JSON parsing error, JSON: \(value)")))
}
case .failure(let error): return.failure(error)
}
}
return response(responseSerializer: responseSerializer, com
pletionHandler: completionHandler)
}
}
this get me 2 errors that at the moment I have not found any way to fix them:
1) for "return .failure(Error(.errorWithCode(.JSONSerializationFailed, failureReason: "JSON parsing error, JSON: \(value)")))", I am getting this error ('Error' cannot be constructed because it as no accessible initializers)
2) for "return response(responseSerializer: responseSerializer, completionHandler: completionHandler)", I am getting this error (Cannot call value of non-function type 'HTTPURLResponse')
Hopefully if any one can point me to a better solution then this bit of code or the correct fix for this. Thanks I will be working on this still, if I do fix it I will update this ticket.
Edit - update
So This is the code as of now
extension Alamofire.Request {
public func responseDecodable<T: Decodable>(completionHandler: #escaping (Response<T>) -> Void) -> Self {
let responseSerializer = ResponseSerializer<T> { request, response, data, error in
guard error == nil else {
print("error Network request: \(error)")
return .failure(error!)
}
let result = Alamofire
.Request
.JSONResponseSerializer(options: .allowFragments)
.serializeResponse(request, response, data, error)
switch result {
case .success(let value):
do {
let decodableObject = try T.decode(value)
return .success(decodableObject)
} catch let decodeErr {
print(decodeErr)
let failureReason = "JSON parsing error, JSON: \(value)"
let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
let error = NSError(domain: "com.prospects.error", code: BackendError.JSONSerializationFailed.rawValue, userInfo: userInfo)
return .failure(error)
}
case .failure(let error): return.failure(error)
}
}
return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
}
}
public protocol ResponseObjectSerializable {
init?(response: HTTPURLResponse, representation: AnyObject)
}
as per last comment : Response and ResponseSerializer are now unresolved, this used to work with no error yesterday. but updating xcode and alamofire this morning as made this to get errors now.
I have the following code snippet to load an image from an url:
let url = NSURL(string: imageUrl)
let data = NSData(contentsOfURL: url!)
let image = UIImage(data: data!)
In case that my variable imageUrl has a valid string value, what is the most secure way to protect this code against possible edge cases?
Following code seems not to be very handy:
if let url = NSURL(string: imageUrl) {
if let data = NSData(contentsOfURL: url) {
if let image = UIImage(data: data) {
// success -> do something with the image...
}
else {
// print error message
}
}
else {
// print error message
}
}
else {
// print error message
}
The best practice is not to use a synchronous method like contentsOfURL to load data from over the network.
The recommended way is NSURLSession which works asynchronously.
This is a simple example with a completion block and an enum with associated types,
it catches all possible errors
enum Result {
case Success(UIImage), Failure(NSString)
}
func loadImage(string : String, completion: (Result) -> ()) {
guard let url = NSURL(string: string) else {
completion(.Failure("Bad URL"))
return
}
NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, error) in
if error != nil {
completion(.Failure(error!.localizedDescription))
} else {
guard let image = UIImage(data: data!) else {
completion(.Failure("Could not load image data"))
return
}
completion(.Success(image))
}
}.resume()
}
Call it with:
loadImage("http://myserver.com/path/to/image.png") { result in
switch result {
case .Success(let image) :
// do something with the image
case .Failure(let error) :
print(error)
}
}