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:
- NSURLSession dispatch queue
- TaskDelegate dispatch queue for validation and serializer processing
- Main dispatch queue for calling your completion handler
- High priority queue for JSON handling
- 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.
- NSURLSession dispatch queue
- TaskDelegate dispatch queue for validation and serializer processing
- Custom manager concurrent dispatch queue for JSON handling
- 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
Can the Height of the Uisearchbar Textfield Be Modified
Difference Between 2 Dates in Weeks and Days Using Swift 3 and Xcode 8
Building Pure Swift Cocoa Touch Framework
Swift: Property Conforming to a Specific Class and in the Same Time to Multiple Protocols
Simple Way to Read Local File Using Swift
Swift - Unit Testing Private Variables and Methods
How to Create Dictionary That Can Hold Anything in Key? or All the Possible Type It Capable to Hold
Lazy Initialisation and Retain Cycle
A Swift Protocol Requirement That Can Only Be Satisfied by Using a Final Class
What's the Difference Between Aranchor and Anchorentity
Xcode 7.3/Swift 2: "No Method Declared with Objective-C Selector" Warning
Do Swift Inner Classes Have Access to Self of Outer Class
How to Create an Instance of a Class from a String in Swift
Generating Resource_Bundle_Accessor, Type 'Bundle' Has No Member 'Module'
Add "For In" Support to Iterate Over Swift Custom Classes
Filter Array of [Anyobject] in Swift
How to Include .Swift File from Other .Swift File in an Immediate Mode