Chain multiple Alamofire requests - ios

I'm looking for a good pattern with which I can chain multiple HTTP requests. I want to use Swift, and preferrably Alamofire.
Say, for example, I want to do the following:
Make a PUT request
Make a GET request
Reload table with data
It seems that the concept of promises may be a good fit for this. PromiseKit could be a good option if I could do something like this:
NSURLConnection.promise(
Alamofire.request(
Router.Put(url: "http://httbin.org/put")
)
).then { (request, response, data, error) in
Alamofire.request(
Router.Get(url: "http://httbin.org/get")
)
}.then { (request, response, data, error) in
// Process data
}.then { () -> () in
// Reload table
}
but that's not possible or at least I'm not aware of it.
How can I achieve this functionality without nesting multiple methods?
I'm new to iOS so maybe there's something more fundamental that I'm missing. What I've done in other frameworks such as Android is to perform these operations in a background process and make the requests synchronous. But Alamofire is inherently asynchronous, so that pattern is not an option.

Wrapping other asynchronous stuff in promises works like this:
func myThingy() -> Promise<AnyObject> {
return Promise{ fulfill, reject in
Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"]).response { (_, _, data, error) in
if error == nil {
fulfill(data)
} else {
reject(error)
}
}
}
}
Edit: Nowadays, use: https://github.com/PromiseKit/Alamofire-

I wrote a class which handles a chain of request one by one.
I created a class RequestChain wich takes Alamofire.Request as parameter
class RequestChain {
typealias CompletionHandler = (success:Bool, errorResult:ErrorResult?) -> Void
struct ErrorResult {
let request:Request?
let error:ErrorType?
}
private var requests:[Request] = []
init(requests:[Request]) {
self.requests = requests
}
func start(completionHandler:CompletionHandler) {
if let request = requests.first {
request.response(completionHandler: { (_, _, _, error) in
if error != nil {
completionHandler(success: false, errorResult: ErrorResult(request: request, error: error))
return
}
self.requests.removeFirst()
self.start(completionHandler)
})
request.resume()
}else {
completionHandler(success: true, errorResult: nil)
return
}
}
}
And I use it like this
let r1 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("1")
}
let r2 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("2")
}
let r3 = Alamofire.request(Router.Countries).responseArray(keyPath: "endpoints") { (response: Response<[CountryModel],NSError>) in
print("3")
}
let chain = RequestChain(requests: [r1,r2,r3])
chain.start { (success, errorResult) in
if success {
print("all have been success")
}else {
print("failed with error \(errorResult?.error) for request \(errorResult?.request)")
}
}
Importent is that you are telling the Manager to not execute the request immediately
let manager = Manager.sharedInstance
manager.startRequestsImmediately = false
Hope it will help someone else
Swift 3.0 Update
class RequestChain {
typealias CompletionHandler = (_ success:Bool, _ errorResult:ErrorResult?) -> Void
struct ErrorResult {
let request:DataRequest?
let error:Error?
}
fileprivate var requests:[DataRequest] = []
init(requests:[DataRequest]) {
self.requests = requests
}
func start(_ completionHandler:#escaping CompletionHandler) {
if let request = requests.first {
request.response(completionHandler: { (response:DefaultDataResponse) in
if let error = response.error {
completionHandler(false, ErrorResult(request: request, error: error))
return
}
self.requests.removeFirst()
self.start(completionHandler)
})
request.resume()
}else {
completionHandler(true, nil)
return
}
}
}
Usage Example Swift 3
/// set Alamofire default manager to start request immediatly to false
SessionManager.default.startRequestsImmediately = false
let firstRequest = Alamofire.request("https://httpbin.org/get")
let secondRequest = Alamofire.request("https://httpbin.org/get")
let chain = RequestChain(requests: [firstRequest, secondRequest])
chain.start { (done, error) in
}

You have multiple options.
Option 1 - Nesting Calls
func runTieredRequests() {
let putRequest = Alamofire.request(.PUT, "http://httpbin.org/put")
putRequest.response { putRequest, putResponse, putData, putError in
let getRequest = Alamofire.request(.GET, "http://httpbin.org/get")
getRequest.response { getRequest, getResponse, getData, getError in
// Process data
// Reload table
}
}
}
This is definitely the approach I would recommend. Nesting one call into another is very simple and is pretty easy to follow. It also keeps things simple.
Option 2 - Splitting into Multiple Methods
func runPutRequest() {
let putRequest = Alamofire.request(.PUT, "http://httpbin.org/put")
putRequest.response { [weak self] putRequest, putResponse, putData, putError in
if let strongSelf = self {
// Probably store some data
strongSelf.runGetRequest()
}
}
}
func runGetRequest() {
let getRequest = Alamofire.request(.GET, "http://httpbin.org/get")
getRequest.response { [weak self] getRequest, getResponse, getData, getError in
if let strongSelf = self {
// Probably store more data
strongSelf.processResponse()
}
}
}
func processResponse() {
// Process that data
}
func reloadData() {
// Reload that data
}
This option is less dense and splits things up into smaller chunks. Depending on your needs and the complexity of your response parsing, this may be a more readable approach.
Option 3 - PromiseKit and Alamofire
Alamofire can handle this pretty easily without having to pull in PromiseKit. If you really want to go this route, you can use the approach provided by #mxcl.

Here is another way to do this (Swift 3, Alamofire 4.x) using a DispatchGroup
import Alamofire
struct SequentialRequest {
static func fetchData() {
let authRequestGroup = DispatchGroup()
let requestGroup = DispatchGroup()
var results = [String: String]()
//First request - this would be the authentication request
authRequestGroup.enter()
Alamofire.request("http://httpbin.org/get").responseData { response in
print("DEBUG: FIRST Request")
results["FIRST"] = response.result.description
if response.result.isSuccess { //Authentication successful, you may use your own tests to confirm that authentication was successful
authRequestGroup.enter() //request for data behind authentication
Alamofire.request("http://httpbin.org/get").responseData { response in
print("DEBUG: SECOND Request")
results["SECOND"] = response.result.description
authRequestGroup.leave()
}
authRequestGroup.enter() //request for data behind authentication
Alamofire.request("http://httpbin.org/get").responseData { response in
print("DEBUG: THIRD Request")
results["THIRD"] = response.result.description
authRequestGroup.leave()
}
}
authRequestGroup.leave()
}
//This only gets executed once all the requests in the authRequestGroup are done (i.e. FIRST, SECOND AND THIRD requests)
authRequestGroup.notify(queue: DispatchQueue.main, execute: {
// Here you can perform additional request that depends on data fetched from the FIRST, SECOND or THIRD requests
requestGroup.enter()
Alamofire.request("http://httpbin.org/get").responseData { response in
print("DEBUG: FOURTH Request")
results["FOURTH"] = response.result.description
requestGroup.leave()
}
//Note: Any code placed here will be executed before the FORTH request completes! To execute code after the FOURTH request, we need the request requestGroup.notify like below
print("This gets executed before the FOURTH request completes")
//This only gets executed once all the requests in the requestGroup are done (i.e. FORTH request)
requestGroup.notify(queue: DispatchQueue.main, execute: {
//Here, you can update the UI, HUD and turn off the network activity indicator
for (request, result) in results {
print("\(request): \(result)")
}
print("DEBUG: all Done")
})
})
}
}

Details
Alamofire 4.7.2
PromiseKit 6.3.4
Xcode 9.4.1
Swift 4.1
Full Sample
NetworkService
import Foundation
import Alamofire
import PromiseKit
class NetworkService {
static fileprivate let queue = DispatchQueue(label: "requests.queue", qos: .utility)
fileprivate class func make(request: DataRequest) -> Promise <(json: [String: Any]?, error: Error?)> {
return Promise <(json: [String: Any]?, error: Error?)> { seal in
request.responseJSON(queue: queue) { response in
// print(response.request ?? "nil") // original URL request
// print(response.response ?? "nil") // HTTP URL response
// print(response.data ?? "nil") // server data
//print(response.result ?? "nil") // result of response serialization
switch response.result {
case .failure(let error):
DispatchQueue.main.async {
seal.fulfill((nil, error))
}
case .success(let data):
DispatchQueue.main.async {
seal.fulfill(((data as? [String: Any]) ?? [:], nil))
}
}
}
}
}
class func searchRequest(term: String) -> Promise<(json: [String: Any]?, error: Error?)>{
let request = Alamofire.request("https://itunes.apple.com/search?term=\(term.replacingOccurrences(of: " ", with: "+"))")
return make(request: request)
}
}
Main func
func run() {
_ = firstly {
return Promise<Void> { seal in
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + .seconds(2)) {
print("1 task finished")
DispatchQueue.main.async {
seal.fulfill(Void())
}
}
}
}.then {
return NetworkService.searchRequest(term: "John").then { json, error -> Promise<Void> in
print("2 task finished")
//print(error ?? "nil")
//print(json ?? "nil")
return Promise { $0.fulfill(Void())}
}
}.then {_ -> Promise<Bool> in
print("Update UI")
return Promise { $0.fulfill(true)}
}.then { previousResult -> Promise<Void> in
print("previous result: \(previousResult)")
return Promise { $0.fulfill(Void())}
}
}
Result

You can use the when method in PromiseKit to attach/append as many calls you want.
Here's an example from PromiseKit docs:
firstly {
when(fulfilled: operation1(), operation2())
}.done { result1, result2 in
//…
}
It worked perfectly for me and it's a much cleaner solution.

Call itself infinitely and DEFINE END CONDITION.
urlring for API link and Dictionary for json
WE may construct the queue model or delegate
func getData(urlring : String , para : Dictionary<String, String>) {
if intCount > 0 {
Alamofire.request( urlring,method: .post, parameters: para , encoding: JSONEncoding.default, headers: nil) .validate()
.downloadProgress {_ in
}
.responseSwiftyJSON {
dataResponse in
switch dataResponse.result {
case .success(let json):
print(json)
let loginStatus : String = json["login_status"].stringValue
print(loginStatus)
if loginStatus == "Y" {
print("go this")
print("login success : int \(self.intCount)")
self.intCount-=1
self.getData(urlring: urlring , para : para)
}
case .failure(let err) :
print(err.localizedDescription)
}
}
}else{
//end condition workout
}
}

Related

exiting switch case doesnt save values [duplicate]

I have created a utility class in my Swift project that handles all the REST requests and responses. I have built a simple REST API so I can test my code. I have created a class method that needs to return an NSArray but because the API call is async I need to return from the method inside the async call. The problem is the async returns void.
If I were doing this in Node I would use JS promises but I can't figure out a solution that works in Swift.
import Foundation
class Bookshop {
class func getGenres() -> NSArray {
println("Hello inside getGenres")
let urlPath = "http://creative.coventry.ac.uk/~bookshop/v1.1/index.php/genre/list"
println(urlPath)
let url: NSURL = NSURL(string: urlPath)
let session = NSURLSession.sharedSession()
var resultsArray:NSArray!
let task = session.dataTaskWithURL(url, completionHandler: {data, response, error -> Void in
println("Task completed")
if(error) {
println(error.localizedDescription)
}
var err: NSError?
var options:NSJSONReadingOptions = NSJSONReadingOptions.MutableContainers
var jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: options, error: &err) as NSDictionary
if(err != nil) {
println("JSON Error \(err!.localizedDescription)")
}
//NSLog("jsonResults %#", jsonResult)
let results: NSArray = jsonResult["genres"] as NSArray
NSLog("jsonResults %#", results)
resultsArray = results
return resultsArray // error [anyObject] is not a subType of 'Void'
})
task.resume()
//return "Hello World!"
// I want to return the NSArray...
}
}
You can pass callback, and call callback inside async call
something like:
class func getGenres(completionHandler: (genres: NSArray) -> ()) {
...
let task = session.dataTaskWithURL(url) {
data, response, error in
...
resultsArray = results
completionHandler(genres: resultsArray)
}
...
task.resume()
}
and then call this method:
override func viewDidLoad() {
Bookshop.getGenres {
genres in
println("View Controller: \(genres)")
}
}
Introduced in Swift 5.5 (iOS 15, macOS 12), we would now use the async-await pattern:
func fetchGenres() async throws -> [Genre] {
…
let (data, _) = try await URLSession.shared.dataTask(for: request)
return try JSONDecoder().decode([Genre].self, from: data)
}
And we would call it like:
let genres = try await fetchGenres()
The async-await syntax is far more concise and natural than the traditional completion handler pattern outlined in my original answer, below.
For more information, see Meet async/await in Swift.
The historic pattern is to use completion handlers closure.
For example, we would often use Result:
func fetchGenres(completion: #escaping (Result<[Genre], Error>) -> Void) {
...
URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error {
DispatchQueue.main.async {
completion(.failure(error))
}
return
}
// parse response here
let results = ...
DispatchQueue.main.async {
completion(.success(results))
}
}.resume()
}
And you’d call it like so:
fetchGenres { results in
switch results {
case .failure(let error):
print(error.localizedDescription)
case .success(let genres):
// use `genres` here, e.g. update model and UI
}
}
// but don’t try to use `genres` here, as the above runs asynchronously
Note, above I’m dispatching the completion handler back to the main queue to simplify model and UI updates. Some developers take exception to this practice and either use whatever queue URLSession used or use their own queue (requiring the caller to manually synchronize the results themselves).
But that’s not material here. The key issue is the use of completion handler to specify the block of code to be run when the asynchronous request is done.
Note, above I retired the use of NSArray (we don’t use those bridged Objective-C types any more). I assume that we had a Genre type and we presumably used JSONDecoder, rather than JSONSerialization, to decode it. But this question didn’t have enough information about the underlying JSON to get into the details here, so I omitted that to avoid clouding the core issue, the use of closures as completion handlers.
Swiftz already offers Future, which is the basic building block of a Promise. A Future is a Promise that cannot fail (all terms here are based on the Scala interpretation, where a Promise is a Monad).
https://github.com/maxpow4h/swiftz/blob/master/swiftz/Future.swift
Hopefully will expand to a full Scala-style Promise eventually (I may write it myself at some point; I'm sure other PRs would be welcome; it's not that difficult with Future already in place).
In your particular case, I would probably create a Result<[Book]> (based on Alexandros Salazar's version of Result). Then your method signature would be:
class func fetchGenres() -> Future<Result<[Book]>> {
Notes
I do not recommend prefixing functions with get in Swift. It will break certain kinds of interoperability with ObjC.
I recommend parsing all the way down to a Book object before returning your results as a Future. There are several ways this system can fail, and it's much more convenient if you check for all of those things before wrapping them up into a Future. Getting to [Book] is much better for the rest of your Swift code than handing around an NSArray.
Swift 4.0
For async Request-Response you can use completion handler. See below I have modified the solution with completion handle paradigm.
func getGenres(_ completion: #escaping (NSArray) -> ()) {
let urlPath = "http://creative.coventry.ac.uk/~bookshop/v1.1/index.php/genre/list"
print(urlPath)
guard let url = URL(string: urlPath) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary {
let results = jsonResult["genres"] as! NSArray
print(results)
completion(results)
}
} catch {
//Catch Error here...
}
}
task.resume()
}
You can call this function as below:
getGenres { (array) in
// Do operation with array
}
Swift 3 version of #Alexey Globchastyy's answer:
class func getGenres(completionHandler: #escaping (genres: NSArray) -> ()) {
...
let task = session.dataTask(with:url) {
data, response, error in
...
resultsArray = results
completionHandler(genres: resultsArray)
}
...
task.resume()
}
Swift 5.5, async/wait-based solution
The original test URL provided by the original poster is no longer functional, so I had to change things a bit. This solution is based on a jokes API I found. That API returns a single joke, but I return it as an array of String ([String]), to keep it as consistent as possible with the original post.
class Bookshop {
class func getGenres() async -> [String] {
print("Hello inside getGenres")
let urlPath = "https://geek-jokes.sameerkumar.website/api?format=json"
print(urlPath)
let url = URL(string: urlPath)!
let session = URLSession.shared
typealias Continuation = CheckedContinuation<[String], Never>
let genres = await withCheckedContinuation { (continuation: Continuation) in
let task = session.dataTask(with: url) { data, response, error in
print("Task completed")
var result: [String] = []
defer {
continuation.resume(returning: result)
}
if let error = error {
print(error.localizedDescription)
return
}
guard let data = data else {
return
}
do {
let jsonResult = try JSONSerialization.jsonObject(with: data, options: [.mutableContainers])
print("jsonResult is \(jsonResult)")
if let joke = (jsonResult as? [String: String])?["joke"] {
result = [joke]
}
} catch {
print("JSON Error \(error.localizedDescription)")
print("data was \(String(describing: String(data: data, encoding: .utf8)))")
return
}
}
task.resume()
}
return genres
}
}
async {
let final = await Bookshop.getGenres()
print("Final is \(final)")
}
The withCheckedContinuation is how you made the Swift async function actually run in a separate task/thread.
I hope you're not still stuck on this, but the short answer is that you can't do this in Swift.
An alternative approach would be to return a callback that will provide the data you need as soon as it is ready.
There are 3 ways of creating call back functions namely:
1. Completion handler
2. Notification
3. Delegates
Completion Handler
Inside set of block is executed and returned when source is available, Handler will wait until response comes so that UI can be updated after.
Notification
Bunch of information is triggered over all the app, Listner can retrieve n make use of that info. Async way of getting info through out the project.
Delegates
Set of methods will get triggered when delegate is been called, Source must be provided via methods itself
Swift 5.5:
TL;DR: Swift 5.5 is not yet released(at the time of writing). To use swift 5.5, download swift toolchain development snapshot from here and add compiler flag -Xfrontend -enable-experimental-concurrency. Read more here
This can be achieved easily with async/await feature.
To do so, you should mark your function as async then do the operation inside withUnsafeThrowingContinuation block like following.
class Bookshop {
class func getGenres() async throws -> NSArray {
print("Hello inside getGenres")
let urlPath = "http://creative.coventry.ac.uk/~bookshop/v1.1/index.php/genre/list"
print(urlPath)
let url = URL(string: urlPath)!
let session = URLSession.shared
return try await withUnsafeThrowingContinuation { continuation in
let task = session.dataTask(with: url, completionHandler: {data, response, error -> Void in
print("Task completed")
if(error != nil) {
print(error!.localizedDescription)
continuation.resume(throwing: error!)
return
}
do {
let jsonResult = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String: Any]
let results: NSArray = jsonResult!["genres"] as! NSArray
continuation.resume(returning: results)
} catch {
continuation.resume(throwing: error)
}
})
task.resume()
}
}
}
And you can call this function like
#asyncHandler
func check() {
do {
let genres = try await Bookshop.getGenres()
print("Result: \(genres)")
} catch {
print("Error: \(error)")
}
}
Keep in mind that, when calling Bookshop.getGenres method, the caller method should be either async or marked as #asyncHandler
self.urlSession.dataTask(with: request, completionHandler: { (data, response, error) in
self.endNetworkActivity()
var responseError: Error? = error
// handle http response status
if let httpResponse = response as? HTTPURLResponse {
if httpResponse.statusCode > 299 , httpResponse.statusCode != 422 {
responseError = NSError.errorForHTTPStatus(httpResponse.statusCode)
}
}
var apiResponse: Response
if let _ = responseError {
apiResponse = Response(request, response as? HTTPURLResponse, responseError!)
self.logError(apiResponse.error!, request: request)
// Handle if access token is invalid
if let nsError: NSError = responseError as NSError? , nsError.code == 401 {
DispatchQueue.main.async {
apiResponse = Response(request, response as? HTTPURLResponse, data!)
let message = apiResponse.message()
// Unautorized access
// User logout
return
}
}
else if let nsError: NSError = responseError as NSError? , nsError.code == 503 {
DispatchQueue.main.async {
apiResponse = Response(request, response as? HTTPURLResponse, data!)
let message = apiResponse.message()
// Down time
// Server is currently down due to some maintenance
return
}
}
} else {
apiResponse = Response(request, response as? HTTPURLResponse, data!)
self.logResponse(data!, forRequest: request)
}
self.removeRequestedURL(request.url!)
DispatchQueue.main.async(execute: { () -> Void in
completionHandler(apiResponse)
})
}).resume()
There are mainly 3 ways of achieving callback in swift
Closures/Completion handler
Delegates
Notifications
Observers can also be used to get notified once the async task has been completed.
There are some very generic requirements that would like every good API Manager to satisfy:
will implement a protocol-oriented API Client.
APIClient Initial Interface
protocol APIClient {
func send(_ request: APIRequest,
completion: #escaping (APIResponse?, Error?) -> Void)
}
protocol APIRequest: Encodable {
var resourceName: String { get }
}
protocol APIResponse: Decodable {
}
Now Please check complete api structure
// ******* This is API Call Class *****
public typealias ResultCallback<Value> = (Result<Value, Error>) -> Void
/// Implementation of a generic-based API client
public class APIClient {
private let baseEndpointUrl = URL(string: "irl")!
private let session = URLSession(configuration: .default)
public init() {
}
/// Sends a request to servers, calling the completion method when finished
public func send<T: APIRequest>(_ request: T, completion: #escaping ResultCallback<DataContainer<T.Response>>) {
let endpoint = self.endpoint(for: request)
let task = session.dataTask(with: URLRequest(url: endpoint)) { data, response, error in
if let data = data {
do {
// Decode the top level response, and look up the decoded response to see
// if it's a success or a failure
let apiResponse = try JSONDecoder().decode(APIResponse<T.Response>.self, from: data)
if let dataContainer = apiResponse.data {
completion(.success(dataContainer))
} else if let message = apiResponse.message {
completion(.failure(APIError.server(message: message)))
} else {
completion(.failure(APIError.decoding))
}
} catch {
completion(.failure(error))
}
} else if let error = error {
completion(.failure(error))
}
}
task.resume()
}
/// Encodes a URL based on the given request
/// Everything needed for a public request to api servers is encoded directly in this URL
private func endpoint<T: APIRequest>(for request: T) -> URL {
guard let baseUrl = URL(string: request.resourceName, relativeTo: baseEndpointUrl) else {
fatalError("Bad resourceName: \(request.resourceName)")
}
var components = URLComponents(url: baseUrl, resolvingAgainstBaseURL: true)!
// Common query items needed for all api requests
let timestamp = "\(Date().timeIntervalSince1970)"
let hash = "\(timestamp)"
let commonQueryItems = [
URLQueryItem(name: "ts", value: timestamp),
URLQueryItem(name: "hash", value: hash),
URLQueryItem(name: "apikey", value: "")
]
// Custom query items needed for this specific request
let customQueryItems: [URLQueryItem]
do {
customQueryItems = try URLQueryItemEncoder.encode(request)
} catch {
fatalError("Wrong parameters: \(error)")
}
components.queryItems = commonQueryItems + customQueryItems
// Construct the final URL with all the previous data
return components.url!
}
}
// ****** API Request Encodable Protocol *****
public protocol APIRequest: Encodable {
/// Response (will be wrapped with a DataContainer)
associatedtype Response: Decodable
/// Endpoint for this request (the last part of the URL)
var resourceName: String { get }
}
// ****** This Results type Data Container Struct ******
public struct DataContainer<Results: Decodable>: Decodable {
public let offset: Int
public let limit: Int
public let total: Int
public let count: Int
public let results: Results
}
// ***** API Errro Enum ****
public enum APIError: Error {
case encoding
case decoding
case server(message: String)
}
// ****** API Response Struct ******
public struct APIResponse<Response: Decodable>: Decodable {
/// Whether it was ok or not
public let status: String?
/// Message that usually gives more information about some error
public let message: String?
/// Requested data
public let data: DataContainer<Response>?
}
// ***** URL Query Encoder OR JSON Encoder *****
enum URLQueryItemEncoder {
static func encode<T: Encodable>(_ encodable: T) throws -> [URLQueryItem] {
let parametersData = try JSONEncoder().encode(encodable)
let parameters = try JSONDecoder().decode([String: HTTPParam].self, from: parametersData)
return parameters.map { URLQueryItem(name: $0, value: $1.description) }
}
}
// ****** HTTP Pamater Conversion Enum *****
enum HTTPParam: CustomStringConvertible, Decodable {
case string(String)
case bool(Bool)
case int(Int)
case double(Double)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let string = try? container.decode(String.self) {
self = .string(string)
} else if let bool = try? container.decode(Bool.self) {
self = .bool(bool)
} else if let int = try? container.decode(Int.self) {
self = .int(int)
} else if let double = try? container.decode(Double.self) {
self = .double(double)
} else {
throw APIError.decoding
}
}
var description: String {
switch self {
case .string(let string):
return string
case .bool(let bool):
return String(describing: bool)
case .int(let int):
return String(describing: int)
case .double(let double):
return String(describing: double)
}
}
}
/// **** This is your API Request Endpoint Method in Struct *****
public struct GetCharacters: APIRequest {
public typealias Response = [MyCharacter]
public var resourceName: String {
return "characters"
}
// Parameters
public let name: String?
public let nameStartsWith: String?
public let limit: Int?
public let offset: Int?
// Note that nil parameters will not be used
public init(name: String? = nil,
nameStartsWith: String? = nil,
limit: Int? = nil,
offset: Int? = nil) {
self.name = name
self.nameStartsWith = nameStartsWith
self.limit = limit
self.offset = offset
}
}
// *** This is Model for Above Api endpoint method ****
public struct MyCharacter: Decodable {
public let id: Int
public let name: String?
public let description: String?
}
// ***** These below line you used to call any api call in your controller or view model ****
func viewDidLoad() {
let apiClient = APIClient()
// A simple request with no parameters
apiClient.send(GetCharacters()) { response in
response.map { dataContainer in
print(dataContainer.results)
}
}
}
This is a small use case that might be helpful:-
func testUrlSession(urlStr:String, completionHandler: #escaping ((String) -> Void)) {
let url = URL(string: urlStr)!
let task = URLSession.shared.dataTask(with: url){(data, response, error) in
guard let data = data else { return }
if let strContent = String(data: data, encoding: .utf8) {
completionHandler(strContent)
}
}
task.resume()
}
While calling the function:-
testUrlSession(urlStr: "YOUR-URL") { (value) in
print("Your string value ::- \(value)")
}

Cancel DispatchWorkItem outside loop - Swift

I've been using DispatchWorkItem and DispatchQueue to make async requests in my app.
However, I've ran into trouble when trying to abort one of the requests.
I successfully use the workItem.cancel() to change the flag and I then check it where I want to abort. Like this:
for stop in self.userSettingsController.history {
stop.PassingInfo?.removeAll()
if workItem?.isCancelled ?? false {
print("CANCELED")
workItem = nil
break
}
...
However there's one case where I have no loop in which I can keep checking if the cancelled flag changes, so I cannot abort the request using the process above. Here's the code:
let tripQueue = DispatchQueue(label: "tripQueue")
var tripWorkItem: DispatchWorkItem? = nil
tripWorkItem = DispatchWorkItem {
self.soapServices.GetPathsByLineAndDirection(lineCode: self.lineCode!, direction: passingInfo.Direction!) { response in
DispatchQueue.main.async {
self.linePaths = response?.filter({$0.Places.contains(where: {$0.Code == self.singleStopSelected?.Code})})
if realTime {
//Get estimated trip
self.showingRealTime = true
if self.linePaths?.count ?? 0 > 0 {
self.getEstimatedTrip(lineCode: self.lineCode ?? "", direction: passingInfo.Direction ?? 0, stopCode: self.singleStopSelected?.Code ?? "", path: (self.linePaths?.first)!) { updateTripTimes in
//Does not work, as is expected. Just showing what I would like to achieve
if tripWorkItem?.isCancelled ?? false {
tripWorkItem = nil
return
}
if updateTripTimes {
DispatchQueue.main.async {
self.updateTripTimes = true
}
}
}
}
} else {
//Get trip
self.showingRealTime = false
self.getTrip(tripId: passingInfo.Id!)
}
}
}
tripWorkItem = nil
}
self.currentTripWorkItem = tripWorkItem
tripQueue.async(execute: tripWorkItem ?? DispatchWorkItem {})
Is there any way to do this?
Thanks in advance.
p.s: I'm sorry if this is duplicated, but I searched before and I couldn't find the question. I might be using the wrong terms.
Rather than putting your code in a DispatchWorkItem, consider wrapping it in a Operation subclass. You get the same isCancelled Boolean pattern:
class ComputeOperation: Operation {
override func main() {
while ... {
if isCancelled { break }
// do iteration of the calculation
}
// all done
}
}
For your network request, wrap it in an custom AsynchronousOperation subclass (e.g. this implementation), and implement cancel which will cancel the network request. For example:
enum NetworkOperationError: Error {
case unknownError(Data?, URLResponse?)
}
class NetworkOperation: AsynchronousOperation {
var task: URLSessionTask!
init(url: URL, completion: #escaping (Result<Data, Error>) -> Void) {
super.init()
self.task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
let responseData = data,
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
error == nil
else {
DispatchQueue.main.async {
completion(.failure(error ?? NetworkOperationError.unknownError(data, response)))
self.finish()
}
return
}
DispatchQueue.main.async {
completion(.success(responseData))
self.finish()
}
}
}
override func main() {
task.resume()
}
override func cancel() {
super.cancel()
task.cancel()
}
}
Don't get lost in the details of the above example. Just note that
It subclassed AsynchronousOperation;
In the completion handler, it calls finish after calling the completion handler; and
The cancel implementation cancels the asynchronous task.
Then you can define your queue:
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 4 // use whatever you think is reasonable
And add your operations to it:
let operation = NetworkOperation(url: url) { response in
switch response {
case .failure(let error):
// do something with `error`
case .success(let data):
// do something with `data`
}
}
queue.addOperation(operation)
Now, the issue in your GetPathsByLineAndDirection and getEstimatedTrip is that you're not following “cancelable” patterns, namely you don't appear to be returning anything that could be used to cancel the request.
So, let's look at an example. Imagine you had some trivial method like:
func startNetworkRequest(with url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> Void) {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
completion(data, response, error)
}
task.resume()
}
What you'd do is change it to return something that can be canceled, the URLSessionTask in this example:
#discardableResult
func startNetworkRequest(with url: URL, completion: #escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionTask {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
completion(data, response, error)
}
task.resume()
return task
}
Now that’s an asynchronous task that is cancelable (and you can wrap it with the above AsynchronousOperation pattern).

How to use a escaping closure in a func to return some data?

In my Code,I want to return some data in a fun after a network request.
func funcName()->String{
var data = "demo"
DataLoader.fetch { result in
if case .success(let fetchedData) = result {
data = fetchedData
} else {
data = "Fail"
}
}
return data
}
The DataLoader is used to get some data from API, the code just like:
struct DataLoader {
static func fetch(completion: #escaping (Result<String, Error>) -> Void) {
let url = URL(string: "URL LINK")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else {
completion(.failure(error!))
return
}
completion(.success(data!))
}
task.resume()
}
}
But as you know data will always be "demo" because of escaping closure.
So how can I return the data after the network request complete without modify the function funcName's params?
If the parameters of the function are not modified.
I'm new in swift, and really at a loss.Thanks a lot if you could help me out!
You have to modify the funcName also to completion closure, instead of return.
func funcName(completion: #escaping (String) -> Void) {
var data = "demo"
DataLoader.fetch { result in
if case .success(let fetchedData) = result {
data = fetchedData
} else {
data = "Fail"
}
completion(data)
}
}
And usage:
funcName { name in
print(name)
}

Alamofire best way to recall api on failure

I need to recall the api when receiving an specific error say error code -1005.
I want to handle this in Alamofire files so that it can work with all api in project.
I was handling this in ObjC's AFNetworking in dataTaskWithHTTPMethod with below code:-
if (failure)
{
if (error.code == -1005)
{
[self POST:URLString parameters:parameters progress:nil success:success failure:failure];
}
}
Can anyone help me to do this in Alamofire?
lets understand with One Example.
let me guess as you have created one class for managing all the webservice realted stuff there. (if not yet then it will be good to create one for best practice).
Okay now create two typealias for managing response.
here it is :-
here i assume again that you want whole dictionary for success response and error for failure response.
typealias successCompletion = (([String:Any]) -> ())
typealias failureCompletion = ((Error) -> ())
now here its one WSManager Class for handling all the API Related stuff there.
class AlamofireManager {
static func sampleFunctionOfWebService(successCompletion:successCompletion , failureCompletion:failureCompletion) {
if success {
successCompletion(["Key":"success"])
} else {
failureCompletion(-1005 as! Error)
}
}
}
you need to pass the both typealias in function for getting CallBack
in desired class.
Here for only understanding purpose we are going to pass static
dictionary :-> ["Key":"success"] and static -1005 as Error.
now how to use this function in our desired class?
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func wsCalling() {
AlamofireManager.sampleFunctionOfWebService(successCompletion: { (dict) in
print(dict)
}) { (error) in
if error.code == -1005 {
self.wsCalling() // recall API Again
} else {
// your other logic
}
}
}
}
I have not mentioned here for URLSeeionTask and All , its good thing
to manage URLSeeionTask. If you have a instance of URLSeeionTask of
Previous API then cancel it first and then try to recall it again.
Happy Coding.
//MARK: Your method
func hitYourService(){
showActivityIndicator(decision: true, inViewConroller: self, animated: true)
let deviceToken = HelpingClass.userDefaultForKey(key: "deviceToken")
let paramDict = ["email":txtFEmail.text ?? "" ,"password":txtFPassword.text ?? "" ,"device_token":deviceToken]
NetworkManager.instance().hitPostServiceJsonForm(paramDict, myReqURL: ServiceMethods.serviceBaseURL+ServiceMethods.login, unReachable: {
// Here you can handle the case if there is no internet connection
}) { (response) in
showActivityIndicator(decision: false, inViewConroller: self, animated: true)
if response != nil{
if response?["error_code"] as! Int == 0{
//Handle your response
}else{
//Show error
showHudWithMessage(message: response?["error_msg"] as! String, inViewConroller: self, animated: true, hideAfter: 2)
}
}else{
//You can hit back your service
self.hitYourService()
}
}
}
//MARK: Alamofire method to hit service, written in NetworkManager class
func hitPostServiceJsonForm(_ params:Dictionary<String,Any>, myReqURL:String, unReachable:(() -> Void),handler:#escaping ((Dictionary<String,Any>?) -> Void)) {
if networkReachable() == false {
unReachable()
}
var request = URLRequest(url: URL(string: myReqURL)!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
print_debug(object: "ReqParameter: \(String(describing: params))") // http url response
//JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
request.httpBody = try! JSONSerialization.data(withJSONObject: params, options: [])
Alamofire.request(request).responseJSON { response in
// SwiftLoader.hide()
print_debug(object: "Request: \(String(describing: response.request))") // original url request
print_debug(object: "Response: \(String(describing: response.response))") // http url response
print_debug(object: "Result: \(response.result)") // response serialization result
print_debug(object: "Result.value \(String(describing: response.result.value))")
switch response.result {
case .success:
if let jsonDict = response.result.value as? Dictionary<String,Any> {
print_debug(object: "Json Response: \(jsonDict)") // serialized json response
handler(jsonDict)
}
else{
handler(nil)
}
if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
print("Server Response: \(utf8Text)") // original server data as UTF8 string
}
break
case .failure(let error):
handler(nil)
print_debug(object: error)
break
}
}
}

How can I log each request/response using Alamofire?

Is there a way to log each request / response using Alamofire (something similar to AFNetworkActivityLogger) ?
I am aware of Printable, DebugPrintable and Output (cURL) but they are not quite what I am looking for.
There's a sweet little pod for this: https://github.com/konkab/AlamofireNetworkActivityLogger
Add this to your podfile:
pod 'AlamofireNetworkActivityLogger', '~> 2.0'
In your AppDelegate:
import AlamofireNetworkActivityLogger
Then in your didFinishLaunchingWithOptions, add this:
NetworkActivityLogger.shared.level = .debug
NetworkActivityLogger.shared.startLogging()
EDIT:
I've actually encountered crashes with this in production. To be on the safe side, use "build flags" to only use this in debug, something like this:
#if DEBUG
NetworkActivityLogger.shared.level = .debug
NetworkActivityLogger.shared.startLogging()
#endif
Something like this might be what you were looking for:
extension Request {
public func debugLog() -> Self {
#if DEBUG
debugPrint(self)
#endif
return self
}
}
Usage:
Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
.debugLog()
.response {…}
If you want to print all responses, you could write your own response method, similar to the responseObject() method at the top of this tutorial:
http://www.raywenderlich.com/87595/intermediate-alamofire-tutorial
[Update: added below per the request from #trauzti.]
Here's how one might do the responseObject() approach in order to print output on every request.
Caveat lector: I haven't personally tested this code, and would probably make different choices in production. This simply shows how the Wenderlich tutorial code can include debug logging. Also note: since the tutorial is pre-Swift 2.0, I've used the old println() instead of print().
#objc public protocol ResponseObjectSerializable {
init(response: NSHTTPURLResponse, representation: AnyObject)
}
extension Alamofire.Request {
public func responseObject<T: ResponseObjectSerializable>(completionHandler: (NSURLRequest, NSHTTPURLResponse?, T?, NSError?) -> Void) -> Self {
let serializer: Serializer = { (request, response, data) in
#if DEBUG
println("Request: \(request.URL)")
#endif
let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
let (JSON: AnyObject?, serializationError) = JSONSerializer(request, response, data)
if response != nil && JSON != nil {
#if DEBUG
println("Response:")
debugPrint(JSON)
#endif
return (T(response: response!, representation: JSON!), nil)
} else {
#if DEBUG
println("Failed Serialization:")
debugPrint(serializationError)
#endif
return (nil, serializationError)
}
}
return response(serializer: serializer, completionHandler: { (request, response, object, error) in
completionHandler(request, response, object as? T, error)
})
}
}
Since Alamofire 5, the easiest way is to define an EventMonitor subclass:
final class AlamofireLogger: EventMonitor {
func requestDidResume(_ request: Request) {
let body = request.request.flatMap { $0.httpBody.map { String(decoding: $0, as: UTF8.self) } } ?? "None"
let message = """
⚡️ Request Started: \(request)
⚡️ Body Data: \(body)
"""
NSLog(message)
}
func request<Value>(_ request: DataRequest, didParseResponse response: DataResponse<Value>) {
NSLog("⚡️ Response Received: \(response.debugDescription)")
}
}
Then use it on your session:
let session = Session(eventMonitors: [ AlamofireLogger() ])
This sample code was adapted from https://github.com/Alamofire/Alamofire/issues/2867#issuecomment-509662892
Timberjack is what you are looking.
Timberjack is a simple, unintrusive network activity logger. Log every request your app makes, or limit to only those using a certain NSURLSession if you’d prefer. It also works with Alamofire, if that’s your thing.
https://cocoapods.org/pods/Timberjack
usage:
import Alamofire
import Timberjack
class HTTPManager: Alamofire.Manager {
static let sharedManager: HTTPManager = {
let configuration = Timberjack.defaultSessionConfiguration()
let manager = HTTPManager(configuration: configuration)
return manager
}()
}
Adding to above answer
for Alamofire 4.0+ Swift 3
extension DataRequest {
public func LogRequest() -> Self {
//Your logic for logging
return self
}
}
When Requesting
Alamofire.request(requestUrl, method: .post, parameters: parameter, encoding: JSONEncoding.default)
.LogRequest()
.responseJSON { response in
//Do your thing
}
If you want to cancel the request in any case(which was something I wanted) you can self.cancel() anywhere before you return self
in Alamofire 5 URLRequest is created asynchronously which means
extension Request {
public func debugLog() -> Self {
#if DEBUG
debugPrint(self)
#endif
return self
}
}
is not the best solution anymore. instead, calling cURLDescription is recommend as below:
let request = AF.request(<Your request>))
request.cURLDescription { (curl) in
print("CURL \(curl)")
}
request.responseJSON { response in
//Do something with your response...
}
or
extension Request {
public func debugLog() -> Self {
#if DEBUG
cURLDescription(calling: { (curl) in
debugPrint("=======================================")
print(curl)
debugPrint("=======================================")
})
#endif
return self
}
}
SOLUTION FOR SWIFT 3.0+
For Printing Request parameter and headers:
Alamofire.request(url, method: .get, parameters: parameters, headers: headers)
.validate()
.responseObject { (response: DataResponse<T>) in
self.pendingRequests.removeValue(forKey: endPoint)
completion!(response)
if(NetworkConfig.loggingEnable) {
debugPrint("************* printing REQUEST parameter and Headers *************")
debugPrint("RESPONSE : \(response.debugDescription)")
}
}.responseDebugPrint()
For Printing Response . use below extension .
import Foundation
import Alamofire
extension Alamofire.DataRequest {
func responseDebugPrint() -> Self {
if NetworkConfig.loggingEnable {
return responseJSON() {
response in
if let JSON = response.result.value,
let JSONData = try? JSONSerialization.data(withJSONObject: JSON, options: .prettyPrinted),
let prettyString = NSString(data: JSONData, encoding: String.Encoding.utf8.rawValue) {
print(prettyString)
} else if let error = response.result.error {
print("Error Debug Print: \(error.localizedDescription)")
}
}
}
return self
}
}
Small gist for you :
https://gist.github.com/manishpathak99/348f2eb0167c0ff6e12ecd667612bc9b/edit
In Alamofire 5.0.0 I used the answer based on:
https://github.com/Alamofire/Alamofire/issues/2867#issuecomment-509662892
but I had to replace DataResponse by AFDataResponse. For example:
import Alamofire
final class AlamofireLogger: EventMonitor {
func requestDidResume(_ request: Request) {
let allHeaders = request.request.flatMap { $0.allHTTPHeaderFields.map { $0.description } } ?? "None"
let headers = """
⚡️⚡️⚡️⚡️ Request Started: \(request)
⚡️⚡️⚡️⚡️ Headers: \(allHeaders)
"""
NSLog(headers)
let body = request.request.flatMap { $0.httpBody.map { String(decoding: $0, as: UTF8.self) } } ?? "None"
let message = """
⚡️⚡️⚡️⚡️ Request Started: \(request)
⚡️⚡️⚡️⚡️ Body Data: \(body)
"""
NSLog(message)
}
func request<Value>(_ request: DataRequest, didParseResponse response: AFDataResponse<Value>) {
NSLog("⚡️⚡️⚡️⚡️ Response Received: \(response.debugDescription)")
NSLog("⚡️⚡️⚡️⚡️ Response All Headers: \(String(describing: response.response?.allHeaderFields))")
}
}
And then you can use it in the following way:
let session = Session(eventMonitors: [ AlamofireLogger() ])
As it has explained by 0xced in an aforementioned post.
In Alamofire 5 and above you can get curl request details by the below code:
request.cURLDescription(calling: { (curl) in
print(curl)
})
And response/error data:
request.responseDecodable { (response:AFDataResponse<T>) in
switch response.result {
case .success(let value):
var responseMessage : String?
if let data = response.data {
let json = String(data: data, encoding: String.Encoding.utf8)
responseMessage = String(describing: json)
}
print(responseMessage)
break;
case .failure(let error):
var message : String?
if let data = response.data {
let json = String(data: data, encoding: String.Encoding.utf8)
message = String(describing: json)
}
print(message)
break
}
}
Alamofire provides a powerful way to gain insight into all the internal events via the EventMonitor protocol. EventMonitor includes several Alamofire events such as URLSessionDelegate request events. This makes EventMonitor ideal for logging events.
import Alamofire
class NetworkLogger: EventMonitor {
let queue = DispatchQueue(label: "com.company.project.networklogger")
func requestDidFinish(_ request: Request) {
print(request.description)
}
func request<Value>(
_ request: DataRequest,
didParseResponse response: DataResponse<Value, AFError>
) {
guard let data = response.data else {
return
}
if let json = try? JSONSerialization
.jsonObject(with: data, options: .mutableContainers) {
print(json)
}
}
}
Reference: https://www.raywenderlich.com/11668143-alamofire-tutorial-for-ios-advanced-usage#toc-anchor-004

Resources