I'm currently using Alamofire for executing an API call:
Alamofire.request(baseUrl + path,method:rMethod,parameters:parameters,encoding: JSONEncoding.default,headers:headers)
.responseJSON { response in
switch response.result {
case .success(let data):
let json = JSON(data)
onCompletion(json, nil)
case .failure(let error):
print("Request failed with error: \(error)")
onCompletion(nil,error)
}
}
Alamofire is notoriously based on a "Result" enum approach for managing the response (check this article):
public enum Result<T, Error: ErrorType> {
case Success(T)
case Failure(Error)
}
Now, I would like to migrate to a try/catch approach for managing the error scenario, something like this:
Alamofire.request(baseUrl + path,method:rMethod,parameters:parameters,encoding: JSONEncoding.default,headers:headers)
.responseJSON { response in
switch response.result {
case .success(let data):
let json = JSON(data)
onCompletion(json)
case .failure(let error):
throw error
}
}
Obviously this approach doesn't work because Alamofire's responseJSON function doesn't throw:
/// Adds a handler to be called once the request has finished.
///
/// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`.
/// - parameter completionHandler: A closure to be executed once the request has finished.
///
/// - returns: The request.
#discardableResult
public func responseJSON(
queue: DispatchQueue? = nil,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: #escaping (DataResponse<Any>) -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: DataRequest.jsonResponseSerializer(options: options),
completionHandler: completionHandler
)
}
}
Question: is there anyway to throw an exception from inside this closure? I have tried forwarding the error to another function which then throw the exception but this is what I get displayed:
Does this mean that if inside a 3rd party library callback I can never throw an error?
Related
I'm using Alamofire for fetching the data.URLRequestConvertible protocol is using for constructing the request.When calling the URLRequestConvertible enum in requested class function through Alamofire I'm getting an error like (Invalid conversion from throwing function of type '(AFDataResponse) throws -> Void' (aka '(DataResponse<Any, AFError>) throws -> ()') to non-throwing function type '(AFDataResponse) -> Void' (aka '(DataResponse<Any, AFError>) -> ()')).In requested function where i'm fetching the result how i can used the generic?
Q1: Getting an error when fetching the result
Q2: How i can used the generic in a function
URLRequestConvertible enum:
enum Router: URLRequestConvertible{
case getAllDishes
var bseUrl : URL{
return URL(string: "https://yummie.glitch.me/")!
}
var method: HTTPMethod{
switch self {
default:
return .get
}
}
var path:String{
switch self{
case .getAllDishes:
return "dish-categories"
}
}
func asURLRequest() throws -> URLRequest {
let url = bseUrl.appendingPathComponent(path)
var request = URLRequest(url: url)
request.method = method
return request
}
}
Calling requested func:
class NetworkLayer{
class func requested(_ request:Router,completion:#escaping(Result<Data,Error>) -> Void){
ProgressHUD.show() //if response comes that loader run
AF.request(request).responseJSON{ (response) in
switch response.result{
case .success(let data):
do{
let getDishesData = data as? [String:Any]
let resp = try JSONSerialization.data(withJSONObject: getDishesData?["data"], options: .prettyPrinted)
completion(.success(response))
}
case .failure(let error):
completion(.failure(error))
}
}
}
You need to add Do-Catch Statement
catch – If the throwing method fails and raises an error, the execution will fall into this catch block.
class NetworkLayer{
class func requested(_ request:Router,completion:#escaping(Result<Data,Error>) -> Void){
ProgressHUD.show() //if response comes that loader run
AF.request(request).responseJSON{ (response) in
switch response.result{
case .success(let data):
do{
let getDishesData = data as? [String:Any]
let resp = try JSONSerialization.data(withJSONObject: getDishesData?["data"], options: .prettyPrinted)
completion(.success(response))
}catch{
print(error)
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}
One more suggestion for you here no need to do JSONSerialization because responseJSON gives you direct response(That Alamofire will do JSONSerialization).
Final code
class NetworkLayer{
class func requested(_ request:Router,completion:#escaping(Result<Data,Error>) -> Void){
ProgressHUD.show() //if response comes that loader run
AF.request(request).responseJSON{ (response) in
switch response.result{
case .success(let response):
do{
print(response)
completion(.success(response))
}catch{
print(error)
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
}
I have my struct that is called APIManager. This struct has the job to call some APIs and return their values. This is how it is organized :
struct APIManager {
public static func checkEmail(_ email: String, success: #escaping (_ status: String) -> Void, failure: #escaping FailureBlock) {
Alamofire
.request(Router.LoginProcess.checkEmail(email: email))
.validate()
.responseJSON { (response: DataResponse<Any> ) in
switch response.result {
case .success(_):
if let value = response.result.value {
let json = JSON(value)
let userStatus = User.Status(rawValue: json["status"].intValue)
success(userStatus ?? User.Status.unknown)
}
break
case .failure(let error):
failure()
break
}
}
}
public static func block(
reason: User.BlockReason,
success: #escaping () -> Void,
failure: #escaping FailureBlock) {
Alamofire
.request(Router.LoginProcess.block(reason: reason))
.validate()
.responseJSON { (response: DataResponse<Any> ) in
switch response.result {
case .success(_):
success()
break
case .failure(_):
failure()
break
}
}
}
}
As you can see, much of the code could be reused creating a method called call(_ api: API.Type), but the problem would be in the success block, because it is different on each method. How can I achieve a result like the following?
public static func call(_ api: API.Type, success: GenericSuccessBlock, failure: FailureBlock) {
...
}
Where the GenericSuccessBlock will be typed using a block declared as typealias based on the api parameter that will be an enum.
In Swift PromiseKit library there's Alamofire example using a bit strange syntax:
func login(completionHandler: (NSDictionary?, ErrorProtocol?) -> Void {
Alamofire.request(.GET, url, parameters: ["foo": "bar"])
.validate()
.responseJSON { response in
switch response.result {
case .success(let dict):
completionHandler(dict, nil)
case .failure(let error):
completionHandler(nil, error)
}
}
}
response is an Alamofire enum describing two cases with associated values:
public enum Result<Value> {
case success(Value)
case failure(Error)
(...)
What I don't get is what does let mean in each case: line and where does the dict (or error) come from? Is this syntactic sugar for something more verbose but less confusing?
In Swift, enums can have associated values (docs). This means, that you can associate an object with cases. The part (let dict) simply means - take the associated value, and put in in a let constant named dict.
I migrated an app from Swift 2.2 to 3.0 which used an extension method from the Alamofire-SwiftyJSON project on GitHub. Alamofire-SwiftyJSON allows receiving the response from an Alamofire network request converted to a SwiftyJSON instance like this:
Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
.responseSwiftyJSON({ (request, response, json, error) in
print(json) // json is a SwiftyJSON 'JSON' instance
print(error)
})
The Alamofire-SwiftyJSON project was not updated for Swift 3 as of writing this question. I'm looking for an equivalent implementation of the responseSwiftyJSON extension method that works with Swift 3+ and Alamofire 4+.
Extension Methods
This solution incorporates a suggestion for working with Alamofire from the SwiftyJSON readme.
It is based on similar extensions included with Alamofire in ResponseSerialization.swift:
DataRequest.responseJSON(queue:options:completionHandler:)
DataRequest.jsonResponseSerializer(options:)
This solution works with Swift 3 and above. It was tested with Alamofire 4.2+ and SwiftyJSON 3.1.3+.
import Alamofire
import SwiftyJSON
extension DataRequest {
/// Adds a handler to be called once the request has finished.
///
/// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`.
/// - parameter completionHandler: A closure to be executed once the request has finished.
///
/// - returns: The request.
#discardableResult
public func responseSwiftyJSON(
queue: DispatchQueue? = nil,
options: JSONSerialization.ReadingOptions = .allowFragments,
completionHandler: #escaping (DataResponse<JSON>) -> Void) -> Self {
return response(
queue: queue,
responseSerializer: DataRequest.swiftyJSONResponseSerializer(options: options),
completionHandler: completionHandler
)
}
/// Creates a response serializer that returns a SwiftyJSON instance result type constructed from the response data using
/// `JSONSerialization` with the specified reading options.
///
/// - parameter options: The JSON serialization reading options. Defaults to `.allowFragments`.
///
/// - returns: A SwiftyJSON response serializer.
public static func swiftyJSONResponseSerializer(
options: JSONSerialization.ReadingOptions = .allowFragments) -> DataResponseSerializer<JSON> {
return DataResponseSerializer { _, response, data, error in
let result = Request.serializeResponseJSON(options: options, response: response, data: data, error: error)
switch result {
case .success(let value):
return .success(JSON(value))
case .failure(let error):
return .failure(error)
}
}
}
}
Example Use:
Alamofire.request("https://httpbin.org/get").validate().responseSwiftyJSON {
response in
print("Response: \(response)")
switch response.result {
case .success(let json):
// Use SwiftyJSON instance
print("JSON: \(json)")
case .failure(let error):
// Handle error
print("Error: \(error)")
}
}
I am attempting to create a function which will return a list of custom objects, created from parsing JSON. I am using AlamoFire to download the content. I have written this function which, on success, creates an array of locations to be returned. However, the returns are always nil. My code is below:
func fetchLocations() -> [Location]? {
var locations : [Location]?
Alamofire.request(.GET, myURL)
.responseJSON { response in
switch response.result {
case .Success(let data):
locations = createMapLocations(data)
case .Failure(let error):
print("Request failed with error: \(error)")
}
}
return locations
}
I am pretty positive the issue is that the functioning is returning before the network request is complete. I am new to Swift, and unsure how to handle this. Any help would be appreciated!
You can read more about closures/ completion handlers https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html or google.
func fetchLocations(completionHandler: (locations: [Location]?, error: NSError) -> ()) -> () {
var locations : [Location]?
Alamofire.request(.GET, myURL)
.responseJSON { response in
switch response.result {
case .Success(let data):
locations = createMapLocations(data)
completionHandler(locations, error: nil)
case .Failure(let error):
print("Request failed with error: \(error)")
completionHandler(locations: nil, error: error)
}
}
}
Usage
fetchLocations(){
data in
if(data.locations != nil){
//do something witht he data
}else{
//Handle error here
print(data.error)
}
}