Alamofire Asynchronous Completionhandler For Json Request

AlamoFire asynchronous completionHandler for JSON request

This is a really good question. Your approach is perfectly valid. However, Alamofire can actually help you streamline this even more.

Your Example Code Dispatch Queue Breakdown

In you example code, you are jumping between the following dispatch queues:

  1. NSURLSession dispatch queue
  2. TaskDelegate dispatch queue for validation and serializer processing
  3. Main dispatch queue for calling your completion handler
  4. High priority queue for JSON handling
  5. Main dispatch queue to update the user interface (if necessary)

As you can see, you're hopping all over the place. Let's take a look at an alternative approach leveraging a powerful feature inside Alamofire.

Alamofire Response Dispatch Queues

Alamofire has an optimal approach built into it's own low level processing. The single response method that ultimately gets called by all custom response serializers has support for a custom dispatch queue if you choose to use it.

While GCD is amazing at hopping between dispatch queues, you want to avoid jumping to a queue that is busy (e.g. the main thread). By eliminating the jump back to the main thread in the middle of the async processing, you can potentially speed things up considerably. The following example demonstrates how to do this using Alamofire logic straight out-of-the-box.

Alamofire 1.x

let queue = dispatch_queue_create("com.cnoon.manager-response-queue", DISPATCH_QUEUE_CONCURRENT)

let request = Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
request.response(
queue: queue,
serializer: Request.JSONResponseSerializer(options: .AllowFragments),
completionHandler: { _, _, JSON, _ in

// You are now running on the concurrent `queue` you created earlier.
println("Parsing JSON on thread: \(NSThread.currentThread()) is main thread: \(NSThread.isMainThread())")

// Validate your JSON response and convert into model objects if necessary
println(JSON)

// To update anything on the main thread, just jump back on like so.
dispatch_async(dispatch_get_main_queue()) {
println("Am I back on the main thread: \(NSThread.isMainThread())")
}
}
)

Alamofire 3.x (Swift 2.2 and 2.3)

let queue = dispatch_queue_create("com.cnoon.manager-response-queue", DISPATCH_QUEUE_CONCURRENT)

let request = Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
request.response(
queue: queue,
responseSerializer: Request.JSONResponseSerializer(options: .AllowFragments),
completionHandler: { response in
// You are now running on the concurrent `queue` you created earlier.
print("Parsing JSON on thread: \(NSThread.currentThread()) is main thread: \(NSThread.isMainThread())")

// Validate your JSON response and convert into model objects if necessary
print(response.result.value)

// To update anything on the main thread, just jump back on like so.
dispatch_async(dispatch_get_main_queue()) {
print("Am I back on the main thread: \(NSThread.isMainThread())")
}
}
)

Alamofire 4.x (Swift 3)

let queue = DispatchQueue(label: "com.cnoon.response-queue", qos: .utility, attributes: [.concurrent])

Alamofire.request("http://httpbin.org/get", parameters: ["foo": "bar"])
.response(
queue: queue,
responseSerializer: DataRequest.jsonResponseSerializer(),
completionHandler: { response in
// You are now running on the concurrent `queue` you created earlier.
print("Parsing JSON on thread: \(Thread.current) is main thread: \(Thread.isMainThread)")

// Validate your JSON response and convert into model objects if necessary
print(response.result.value)

// To update anything on the main thread, just jump back on like so.
DispatchQueue.main.async {
print("Am I back on the main thread: \(Thread.isMainThread)")
}
}
)

Alamofire Dispatch Queue Breakdown

Here is the breakdown of the different dispatch queues involved with this approach.

  1. NSURLSession dispatch queue
  2. TaskDelegate dispatch queue for validation and serializer processing
  3. Custom manager concurrent dispatch queue for JSON handling
  4. Main dispatch queue to update the user interface (if necessary)

Summary

By eliminating the first hop back to the main dispatch queue, you have eliminated a potential bottleneck as well as you have made your entire request and processing asynchronous. Awesome!

With that said, I can't stress enough how important it is to get familiar with the internals of how Alamofire really works. You never know when you may find something that can really help you improve your own code.

swift alamofire request json asynchronous

Instead of returning JSON? in the method signature, use a completion closure like this:

public func getJSON(fileName: String, completion: ((JSON?) -> Void)?) {
let url = "http://s3.eu-west-3.amazonaws.com" + fileName
Alamofire.request(url).responseJSON { response in
if let result = response.result.value {
completion?(JSON(result))
} else {
completion?(nil)
}
}
}

And call the method like this:

getJSON(fileName: "/jsonsBucket/myJson.json") { json in
print(json)
}

Or:

getJSON(fileName: "/jsonsBucket/myJson.json", completion: { json in
print(json)
})

Handle Alamofire asynchronous request using SwiftyJSON

You need to use completionHandler to return data to TableViewController.

func getObjects(completionHandler : @escaping ([Object]) -> (),_ urlString:String) -> [Sneaker] {
var objects = [Object]()

requestObjects(urlString, success: { (JSONResponse) -> Void in
let json = JSONResponse

for item in json["items"] {
let title = item.1["title"].string
objects.append(Object(title: title!))
}
completionHandler(objects)
print("Number of objects = \(objects.count)")
}) {
(error) -> Void in
print(error)
}
print(objects) // Prints empty array
return objects // Array is empty
}

}

In your TableViewController

dataManager.getObject(completionHandler: { list in
self.objects = list
}, urlString)

There could be some syntax error i didnt test it

Convert Alamofire Completion handler to Async/Await | Swift 5.5, *

First of all, your structure is wrong. Do not start with your original code and wrap all of it in the continuation block. Just make a version of AF.request itself that's wrapped in a continuation block. For example, the JSON decoding is not something that should be part of what's being wrapped; it is what comes after the result of networking returns to you — it is the reason why you want to turn AF.request into an async function to begin with.

Second, as the error message tells you, resolve the generic, either by the returning into an explicit return type, or by stating the type as part of the continuation declaration.

So, for example, what I would do is just minimally wrap AF.request in an async throws function, where if we get the data we return it and if we get an error we throw it:

func afRequest(url:URL) async throws -> Data {
try await withUnsafeThrowingContinuation { continuation in
AF.request(url, method: .get).validate().responseData { response in
if let data = response.data {
continuation.resume(returning: data)
return
}
if let err = response.error {
continuation.resume(throwing: err)
return
}
fatalError("should not get here")
}
}
}

You'll notice that I didn't need to resolve the generic continuation type because I've declared the function's return type. (This is why I pointed you to my explanation and example in my online tutorial on this topic; did you read it?)

Okay, so the point is, now it is trivial to call that function within the async/await world. A possible basic structure is:

func getTokenBalances3() async {
let url = // ...
do {
let data = try await self.afRequest(url:url)
print(data)
// we've got data! okay, so
// do something with the data, like decode it
// if you declare this method as returning the decoded value,
// you could return it
} catch {
print(error)
// we've got an error! okay, so
// do something with the error, like print it
// if you declare this method as throwing,
// you could rethrow it
}
}

Finally I should add that all of this effort is probably wasted anyway, because I would expect the Alamofire people to be along with their own async versions of all their asynchronous methods, any time now.

How to have only 1 completion handler for 2 Alamofire request in the same function

Update your getFeedVideos method as shown below:

func getFeedVideos(completion: @escaping (_ completedDownloadVideo: Bool) -> ()){

Alamofire.request(youTubeUrl, method: .get, parameters: ["part":"snippet","maxResults": "20","nextPageToken" : "pageToken", "playlistId": playListId, "key": googleAPIKey], encoding: URLEncoding.default, headers: nil).responseJSON { (response) in
guard let data = response.data else {return}
do {
let json = try JSONDecoder().decode(serverData.self, from: data)
self.dataFromAPI = json.items

Alamofire.request(youTubeChannelUrl, method: .get, parameters: ["part":"snippet", "key": googleAPIKey, "id": youTubeChannelId], encoding: URLEncoding.default, headers: nil).responseJSON { (response) in
guard let data = response.data else {return}
do {
let json = try JSONDecoder().decode(channelInfo.self, from: data)
self.channelData = json.items
completion(true)
} catch let jsonErr{
print(jsonErr)
}
}
} catch let jsonErr{
print(jsonErr)
}
}
}

As I am seeing in your code you are calling completion(true) in both API calls thats why its reloading multiple times. In my code I have replaced completion(true) from your first API call with another API call so Once your both API call with complete your completion(true) will call.

Hope this will help.

Note:

Didn't checked this code on Xcode so let me know if you got any issue with this code.

CompletionHandler for Alamofire

var res: Any = ""
PersonResource.getAllPersons{ (result) in
res = result
print(res)
}

Put print statement inside it will print the result

Why ?

PersonResource.getAllPersons is an asynchronous call so print(res) gets executed even before completion block of PersonResource.getAllPersons executes and sets res

Return Alamofire response from a function

The main problem is that Alamofire.request is an asynchronous call, so you will need to use a completion handler, like so

func getStoriesNF (completion: @escaping ([String : Any]?, Error?) {

let parameters: Parameters = ["user_id": userID]

Alamofire.request("https://example.com/stories.php", method: .post, parameters: parameters).validate().responseJSON { response in
switch response.result {
case .success:
if let json = response.result.value {
do {
if let data = try JSONSerialization.jsonObject(with: json as Data, options: .allowFragments) as? [String:Any] {
completion(data, nil)
} catch (let error) {
completion(nil, error)
}
}
}
}
}

JSONSerialization.jsonObject needs to be contained within a try-catch block since it's a throwing call



Related Topics



Leave a reply



Submit