Alamofire 3.0.0-beta.3 Image Response Serialization - serialization

Can some one help me please, I'm trying to implement Alamofire image response serialization method : imageResponseSerializer,
Here is my code :
extension Alamofire.Request {
class func imageResponseSerializer() -> ResponseSerializer<UIImage, NSError> {
return ResponseSerializer<UIImage, NSError> { request, response, data, error in
guard let validData = data else {
let failureReason = "Data could not be serialized. Input data was nil."
let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
if let image = UIImage(data: validData, scale: UIScreen.mainScreen().scale) {
return Result<UIImage, NSError>.Success(image)
}
else {
return .Failure(Error.errorWithCode(.JSONSerializationFailed, failureReason: "Unable to create image."))
}
}
}
func responseImage(completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<UIImage, NSError>) -> Void) -> Self {
return response(responseSerializer: Request.imageResponseSerializer(), completionHandler: { request, response, result in
completionHandler(request, response, result)
})
}
}
Error : cannot call value of non-function type 'NSHTTPURLResponse?'
I'm using : Xcode 7.0.1, Swift 2 and Alamofire 3.0.0-beta.3
Thank you,

You should really check out AlamofireImage. 1) It has all this implemented already. 2) It has many other awesome features that you will most likely find handy.

Related

SwiftUI OpenWeatherApi App show no content

Im new to swift development and I can't make myself pass through this. I run my app and by pressing the "action" button I see no change on my text which should be the current temperature. And It's giving me an error "nw_protocol_get_quic_image_block_invoke dlopen libquic failed"
My code:
struct ContentView: View {
#State var temp = Int()
func CallApi(){
let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=budapest&appid=#########################################")
URLSession.shared.dataTask(with: url!){data, response, error in
if let data = data {
if let DecodedData = try? JSONDecoder().decode(Api.self, from: data){
self.temp = DecodedData.main.temp
}
}
}.resume()
}
struct Api: Codable, Hashable{
var main: DataStructre
}
struct DataStructre: Codable, Hashable {
var temp: Int
}
var body: some View {
Text("\(temp)")
Button("Idojaras"){CallApi()}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
If you use do/try/catch instead of try?, you can get a useful error message.
do {
let DecodedData = try JSONDecoder().decode(Api.self, from: data)
self.temp = DecodedData.main.temp
} catch {
print(error)
}
In this case, I get:
Parsed JSON number <304.31> does not fit in Int.
Changing the type of temp (in both the #State variable and the DataStructre) fixes the issue.
In general, I'd recommend pasting your JSON into app.quicktype.io, which will give you generated models with the correct types/structures.
Slightly unrelated to your issue, but you may want to look into using dataTaskPublisher and Combine in SwiftUI rather than dataTask (https://developer.apple.com/documentation/foundation/urlsession/processing_url_session_data_task_results_with_combine). Also, function names in Swift are generally lowercased.

How does a view obtain data using a view model and Network API

I'm trying to fetch some data with this helper file:
https://gist.github.com/jbfbell/e011c5e4c3869584723d79927b7c4b68
Here's a snippet of the important code:
Class
/// Base class for requests to the Alpha Vantage Stock Data API. Intended to be subclasssed, but can
/// be used directly if library does not support a new api.
class AlphaVantageRequest : ApiRequest {
private static let alphaApi = AlphaVantageRestApi()
let method = "GET"
let path = ""
let queryStringParameters : Array<URLQueryItem>
let api : RestApi = AlphaVantageRequest.alphaApi
var responseJSON : [String : Any]? {
didSet {
if let results = responseJSON {
print(results)
}
}
}
}
Extension ApiRequest
/// Makes asynchronous call to fetch response from server, stores response on self
///
/// - Returns: self to allow for chained method calls
public func callApi() -> ApiRequest {
guard let apiRequest = createRequest() else {
print("No Request to make")
return self
}
let session = URLSession(configuration: URLSessionConfiguration.ephemeral)
let dataTask = session.dataTask(with: apiRequest) {(data, response, error) in
guard error == nil else {
print("Error Reaching API, \(String(describing: apiRequest.url))")
return
}
self.receiveResponse(data)
}
dataTask.resume()
return self
}
My goal is to fetch the data from responseJSON after the data of the url request is loaded.
My ViewModel currently looks like this:
class CompanyViewModel: ObservableObject {
var companyOverviewRequest: ApiRequest? {
didSet {
if let response = companyOverviewRequest?.responseJSON {
print(response)
}
}
}
private var searchEndpoint: SearchEndpoint
init(companyOverviewRequest: AlphaVantageRequest? = nil,
searchEndpoint: SearchEndpoint) {
self.companyOverviewRequest = CompanyOverviewRequest(symbol: searchEndpoint.symbol)
}
func fetchCompanyOverview() {
guard let request = self.companyOverviewRequest?.callApi() else { return }
self.companyOverviewRequest = request
}
}
So in my ViewModel the didSet gets called once but not when it should store the data. The results of AlphaVantageRequest always prints out properly, but not in my ViewModel. How can I achieve to have the loaded data also in my ViewModel?
When you use a view model which is an ObservableObject, your view wants to observe published properties, usually a viewState (MVVM terminology):
class CompanyViewModel: ObservableObject {
enum ViewState {
case undefined
case value(Company)
}
#Published var viewState: ViewState = .undefined
viewState completely describes how your view will be rendered. Note, that it can be undefined - which your view should be able to handle.
Adding a loading(Company?) case would also be a good idea. Your view can then render a loading indicator. Note that loading also provides an optional company value. You can then render a "refresh", in which case you already have a company value while also drawing a loading indicator.
In order to fetch some data from an endpoint, you may use the following abstraction:
public protocol HTTPClient: class {
func publisher(for request: URLRequest) -> AnyPublisher<HTTPResponse, Swift.Error>
}
This can be implemented by a simple wrapper around URLSession with 5 lines of code. A conforming type may however do much more: it may handle authentication, authorization, it may retry requests, refresh access tokens, or present user interfaces where the user needs to authenticate, etc. This simple protocol is sufficient for all this.
So, how does your ViewModel get the data?
It makes sense to introduce another abstraction: "UseCase" which performs this task, and not let the view model directly use the HTTP client.
A "use case" is simply an object that performs a task, taking an input and producing an output or error. You can name it how you want, "DataProvider", "ContentProvider" or something like this. "Use Case" is a well known term, though.
Conceptually, it has a similar API as an HTTP client, but semantically it sits on a higher level:
public protocol UseCase {
associatedtype Input: Encodable
associatedtype Output: Decodable
associatedtype Error
func callAsFunction(with input: Input) -> AnyPublisher<Output, Error>
}
Lets create us a "GetCompany" use case:
struct Company: Codable {
var name: String
var id: Int
}
struct GetCompanyUseCase: UseCase {
typealias Input = Int
typealias Output = Company
typealias Error = Swift.Error
private let httpClient: HTTPClient
init(httpClient: HTTPClient) {
self.httpClient = httpClient
}
func callAsFunction(with id: Int) -> AnyPublisher<Company, Swift.Error> {
let request = composeURLRequest(input: id)
return httpClient.publisher(for: request)
.tryMap { httpResponse in
switch httpResponse {
case .success(_, let data):
return data
default:
throw "invalid status code"
}
}
.decode(type: Company.self, decoder: JSONDecoder())
.map { $0 } // no-op, usually you receive a "DTO.Company" value and transform it into your Company type.
.eraseToAnyPublisher()
}
private func composeURLRequest(input: Int) -> URLRequest {
let url = URL(string: "https://api.my.com/companies?id=\(input)")!
return URLRequest(url: url)
}
}
So, this Use Case clearly accesses our HTTP client. We can implement this accessing CoreData, or read from file, or using a mock, etc. The API is always the same, and the view model does not care. The beauty here is, you can switch it out and swap in another one, the view model still works and also your view. (In order to make this really cool, you would create a AnyUseCase generic type, which is very easy, and here you have your dependency injection).
Now lets see how the view model may look like and how it uses the Use Case:
class CompanyViewModel: ObservableObject {
enum ViewState {
case undefined
case value(Company)
}
#Published var viewState: ViewState = .undefined
let getCompany: GetCompanyUseCase
var getCompanyCancellable: AnyCancellable?
init(getCompany: GetCompanyUseCase) {
self.getCompany = getCompany
}
func load() {
self.getCompanyCancellable =
self.getCompany(with: 1)
.sink { (completion) in
print(completion)
} receiveValue: { (company) in
self.viewState = .value(company)
print("company set to: \(company)")
}
}
}
The load function triggers the use case, which calls the underlying http client to load the company data.
When the UseCase returns a company, it will be assigned the view state. Observers (the view, or ViewController) will get notified about the change and can preform an update.
You can experiment with code in playground. Here are the missing peaces:
import Foundation
import Combine
extension String: Swift.Error {}
public enum HTTPResponse {
case information(response: HTTPURLResponse, data: Data)
case success(response: HTTPURLResponse, data: Data)
case redirect(response: HTTPURLResponse, data: Data)
case clientError(response: HTTPURLResponse, data: Data)
case serverError(response: HTTPURLResponse, data: Data)
case custom(response: HTTPURLResponse, data: Data)
}
class MockHTTPClient: HTTPClient {
func publisher(for request: URLRequest) -> AnyPublisher<HTTPResponse, Swift.Error> {
let json = #"{"id": 1, "name": "Some Corporation"}"#.data(using: .utf8)!
let url = URL(string: "https://api.my.com/companies")!
let httpUrlResponse = HTTPURLResponse(url: url, statusCode: 200, httpVersion: nil, headerFields: nil)!
let response: HTTPResponse = .success(response: httpUrlResponse, data: json)
return Just(response)
.mapError { _ in "no error" }
.eraseToAnyPublisher()
}
}
Assemble:
let httpClient = MockHTTPClient()
let getCompany = GetCompany(httpClient: httpClient)
let viewModel = CompanyViewModel(getCompany: getCompany)
viewModel.load()

RxAlamofire - how to get the response on error?

I need the response body when an error occurs in an RxAlamofire call. I've seen this hack but I wonder if there's a cleaner way.
Inspired by it, I created this RxAlamofire fork with a similar hack. With it, errors will usually be an instance of DataResponseError so you can do this:
RxAlamofire.data(method, url).subscribe(
onError: { error in
if let error = error as? DataResponseError<Data> {
// Get response body (in this case, convert it to a String)
if let data = error.response.data {
let message = String(data: data, encoding: String.Encoding.utf8)
print("Message: \(message)")
}
// Get status code
if let statusCode = error.response.response?.statusCode {
print("Status code: \(statusCode)")
}
}
}
)
Issue description. I'm using RxAlamofire to make network requests, and I needed to get information from the body of the error response.
I've made a hack in a folloing way:
Added a PError:
import UIKit
import Alamofire
import ObjectMapper
class PError: Error, Mappable {
var message: String?
var statusCode: Int?
required init?(map: Map) {
}
func mapping( map: Map) {
message <- map["error.message"]
statusCode <- map["error.statusCode"]
}
}
And now added such extensions to DataRequest:
import Alamofire
import ObjectMapper
extension DataRequest {
//Overriding several methods from Alamofire Validation
#discardableResult
public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
return validate { [unowned self] _, response, bodyData in
return self.validate(statusCode: acceptableStatusCodes, response: response, bodyData: bodyData)
}
}
//Overriding several methods from Alamofire Validataion
fileprivate func validate<S: Sequence>(
statusCode acceptableStatusCodes: S,
response: HTTPURLResponse, bodyData: Data?)
-> ValidationResult
where S.Iterator.Element == Int
{
if acceptableStatusCodes.contains(response.statusCode) {
return .success
} else {
var error: Error = AFError.responseValidationFailed(reason: AFError.ResponseValidationFailureReason.unacceptableStatusCode(code: response.statusCode))
if let bodyData = bodyData {
if let jsonString = String(data: bodyData, encoding: .utf8) {
if let errorNew = Mapper<PError>().map(JSONString: jsonString)
{
error = errorNew
}
}
}
return .failure(error)
}
}
}
Next, somewhere in the code you'll be able to work with this custom error object:
if let err = error as? PError {
status = err.message ?? "No message in the error description"
}
else

Converting objective-c block to Swift closure

I am trying to convert this Objective-C block into Swift:
[self.client downloadEntity:#"Students" withParams: nil success:^(id response) {
// execute code
}
failure:^(NSError *error) {
// Execute code
}];
This is my code in Swift, but the syntax seems to be a bit off:
client.downloadEntity("Students", withParams: nil, success: {(students: [AnyObject]!) -> Void in
print("here")
}, failure: { (error: NSError!) -> Void! in
print ("here")
}
This is giving me a few compilation errors:
Value of 'AnyObject' has no member 'downloadEntity'
It is complaining about the lack of commas (,) right after the failure part of the code
Try this:
client.downloadEntity("Student", withParams: nil,
success: { (responseObj) -> Void in
print("success: \(responseObj)")
},
failure: { (errorObj) -> Void in
print("treat here (in this block) the error! error:\(errorObj)")
})
You need to switch to the new Swift error syntax, and you can also using trailing closures. I had to use a bool for the example to show how you would call your success closure, or you would throw an error.
var wasSuccessful = true // This is just here so this compiles and runs
// This is a custom error type. If you are using something that throws an
// NSError, you don't need this.
enum Error:ErrorType {
case DownloadFailed
}
// Hopefully you have control over this method and you can update
// the signature and body to something similar to this:
func downloadEntity(entityName: String, success: ([AnyObject]) -> Void) throws {
let students = [AnyObject]()
// download your entity
if wasSuccessful {
// Call your success completion handler
success(students)
}
else {
throw Error.DownloadFailed
}
}
When you have a function that can throw an error, you need to call it with try inside a do/catch block.
// Calling a function that can throw
do {
try downloadEntity("Students") { students in
print("Download Succeded")
}
}
catch Error.DownloadFailed {
print("Download Failed")
}
// If you are handling NSError use this block instead of the one above
// catch let error as NSError {
// print(error.description)
// }

Load PageViewController After Asynchronous Task Complete

I'm having trouble loading the PageViewController after the async call is complete. I was considering using NSNotification, but not sure what is the best approach.
Async func to fetch images
func fetchArrayImages() {
var query = PFQuery(className: "FoodPhoto")
query.orderByDescending("Votes")
query.findObjectsInBackgroundWithBlock ({(objects:[AnyObject]!, error: NSError!) in
if(error == nil){
let imageObjects = objects as [PFObject]
for object in objects {
let photoUploaded = object["PhotoUploaded"] as PFFile
photoUploaded.getDataInBackgroundWithBlock({
(imageData: NSData!, error: NSError!) -> Void in
if (error == nil) {
let image = UIImage(data:imageData)
//image object implementation
self.photosUploadedArray.append(image!)
}
})
}
}
else{
println("Error in retrieving \(error)")
}
})
}
Func to be called after async download images
This loads the UIPageViewController
func loadPhotosFromArray() {
var array = photosUploadedArray
view1 = PhotoCollevtionView(outerFrame: self.view.frame, photoArray: array, currentNumber: 0)
self.view.addSubview(view1!)
}
You can check your uploaded image is equals with last image in your imageObjects array and in this case you can call your loadPhotosFromArray() code like this:
self.photosUploadedArray.append(image!)
if ( imageObjects.last.isEqual(image!)) { //Use this code
loadPhotosFromArray()
}