Swift: How can I use synchronized thread for Alamofire? - swift

I want to use synchronized thread using Alamofire.
I am using like this code for synchronization.
let queue1 = DispatchQueue(label: "queue1")
let queue2 = DispatchQueue(label: "queue2")
queue1.async {
self.dataFromServer.getData()
}
queue2.async {
//check if success to get the data from server
while(!self.dataFromServer.resultData){}
DispatchQueue.main.async {
// do something on screen
}
}
...
func getData() {
Alamofire.request(url).responseJSON { response in
if response.error == nil {
// get data
self.resultData = true
}else{
// error
}
}
}
I want to do something after get data from server using Alamofire.
Is this correct?
If this is not good, please tell me about the synchronization please.
Kind regards.

There is another and probably the good way of doing it by using closures.
// You don't need to create queue and it asynchronously, since Alamofire calls are async.
// You can remove queue statement
let queue1 = DispatchQueue(label: "queue1")
queue1.async {
self.dataFromServer.getData(completionClosure: { (resultData) in
if resultData {
}
else {
}
})
}
Then in your function have a parameter that takes a closure and call it with success or failure boolean.
func getData(completionClosure: (Bool) -> Void) {
Alamofire.request(url).responseJSON { response in
if response.error == nil {
// get data
completionClosure(true)
}
else{
completionClosure(false)
}
}
}

you can call
dispatch_sync
instead of async or you can call other API in the success block of first API

Modify the getData() function to return block so that you get callback on the sucess of the api call
func getData(sucess:((Void) -> Void)?) {
Alamofire.request(url).responseJSON { response in
if response.error == nil {
// get data
sucess!()
self.resultData = true
}else{
// error
}
}
}
Do whatever you want to do after the api response has received in the sucess callback of the getData() function

Related

Sync closure needs to wait for async code

I am developing an app that uses MPRemoteCommandCenter to control audio reproduction from the lock screen.
The code is inside a #MainActor view-model, part of a SwiftUI project.
The initial code that I need to incorporate is:
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { [unowned self] event in
if !self.isPlaying {
self.playOrPause()
return .success
}
return .commandFailed
}
I have tried this:
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { [unowned self] event in
return await MainActor.run {
if !self.isPlaying {
self.playOrPause()
return .success
}
return .commandFailed
}
}
But still it doesn't compile.
I understand why: the callback method is not marked to be async. Still it expects a value, that I can return only from async code, because I need to wait to re-enter into the main thread.
Can anyone please help me?
You cannot ask for a sync function to wait for an async. You can have however an async function receiving the returned values from other async closures using withCheckedContinuation().
In your case, this is how it might work:
let isSuccessfullyCompleted: Bool = await withCheckedContinuation { continuation in
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { [unowned self] event in
if !self.isPlaying {
self.playOrPause()
continuation.resume(returning: true)
} else {
continuation.resume(returning: false)
}
}
}
With the code above, isSuccessfullyCompleted will wait for the closure to complete and return a value, before resuming the code. The variable doesn't need to be a Bool, but the code above needs to run in an async function.

Swift: Recursive async func with completion handler that does not get called

I need to fetch large amounts of data from an endpoint in an async way. The API endpoint serves a predefined amount of data at a time. After the first request I must check to see if I get a "next" url from the response and visit that link in order to continue the download. This recursive behaviour continues until all available data has been served, in other words paging functionality (HAL links). At this point I have implemented a func that download recursively, however: problem is that the final completion handler does not seem to get called.
Demo code: The ThingsApi is a class that encapsulates the actual API call. The important thing is that this class has an initial url and during recursion will get specific url's to visit asynchronously. I call the downloadThings() func and need to get notified when it is finished. It works if I leave recursion out of the equation. But when recursion is in play then nothing!
I have created a simplified version of the code that illustrate the logic and can be pasted directly into the Playground. The currentPage and pages var's are just there to demo the flow. The last print() statement does not get called. Leave the currentPage += 1 to experience the problem and set currentPage += 6 to avoid recursion. Clearly I am missing out of some fundamental concept here. Anyone?
import UIKit
let pages = 5
var currentPage = 0
class ThingsApi {
var url: URL?
var next: URL?
init(from url: URL) {
self.url = url
}
init() {
self.url = URL(string: "https://whatever.org")
}
func get(completion: #escaping (Data?, HTTPURLResponse?, Error?) -> Void) {
// *** Greatly simplified
// Essentially: use URLSession.shared.dataTask and download data async.
// When done, call the completion handler.
// Simulate that the download will take 1 second.
sleep(1)
completion(nil, nil, nil)
}
}
func downloadThings(url: URL? = nil, completion: #escaping (Bool, Error?, String?) -> Void) {
var thingsApi: ThingsApi
if let url = url {
// The ThingsApi will use the next url (retrieved from previous call).
thingsApi = ThingsApi(from: url)
} else {
// The ThingsApi will use the default url.
thingsApi = ThingsApi()
}
thingsApi.get(completion: { (data, response, error) in
if let error = error {
completion(false, error, "We have nothing")
} else {
// *** Greatly simplified
// Parse the data and save to db.
// Simulate that the thingsApi.next will have a value 5 times.
currentPage += 1
if currentPage <= pages {
thingsApi.next = URL(string: "https://whatever.org?page=\(currentPage)")
}
if let next = thingsApi.next {
// Continue downloading things recursivly.
downloadThings(url: next) { (success, error, feedback) in
guard success else {
completion(false, error, "failed")
return
}
}
} else {
print("We are done")
completion(true, nil, "done")
print("I am sure of it")
}
}
})
}
downloadThings { (success, error, feedback) in
guard success else {
print("downloadThings() failed")
return
}
// THIS DOES NOT GET EXECUTED!
print("All your things have been downloaded")
}
It seems like this is simply a case of "you forgot to call it yourself" :)
In this if statement right here:
if let next = thingsApi.next {
// Continue downloading things recursivly.
downloadThings(url: next) { (success, error, feedback) in
guard success else {
completion(false, error, "failed")
return
}
}
} else {
print("We are done")
completion(true, nil, "done")
print("I am sure of it")
}
Think about what happens on the outermost call to downloadThings, and execution goes into the if branch, and the download is successful. completion is never called!
You should call completion after the guard statement!

Swift service call, handling response

I am writing the iOS application using swift 4.2. I am making a service call to logout user.
I need to know where to use main thread (DispatchQueue.main.async).
Here is my code:
private func handleLogoutCellTap() {
logoutUseCase?.logout() { [weak self] (result) in
guard let self = self else { return }
switch result {
case let (.success(didLogout)):
didLogout ? self.handleSuccessfullLogout() : self.handleLogoutError(with: nil)
case let (.failure(error)):
self.handleLogoutError(with: error)
}
}
}
logoutUseCase?.logout() makes a service call and returns #escaping completion. Should I use DispatchQueue.main.async on this whole handleLogoutCellTap() function or just in a handling segment?
Move the control to main thread wherever you're updating the UI after receiving the response of logout.
If handleSuccessfullLogout() and handleLogoutError(with:) methods perform any UI operation, you can embed the whole switch statement in DispatchQueue.main.async, i,e.
private func handleLogoutCellTap() {
logoutUseCase?.logout() { [weak self] (result) in
guard let self = self else { return }
DispatchQueue.main.async { //here.....
switch result {
//rest of the code....
}
}
}
}

Completion Handler Causes Deinit To Not Be Called

I have a network task that contains a completion handler, allowing me to determine if and when the task completed successfully;
func performUpload(url: URL, completionHandler: #escaping (_ response: Bool) -> ()) {
photoSave.savePhotoRemote(assetURL: url) { response in
completionHandler(response)
}
}
I call this function from another UIView, by using the following;
UploadHelper.shared.performUpload(url: fileAssetPath) { response in
if response {
// do stuff
}
}
What I am noticing is that when I capture the response and do stuff, the class that called this function will never deinit. However, if I change my function to the following;
UploadHelper.shared.performUpload(url: fileAssetPath) { response in }
and don't do anything with the response, the class will deinit.
What am I doing wrong with my function? I would like to capture the response accordingly, but not at the expense of my class not being released from memory.
You've got a retain cycle. Break it. Change
UploadHelper.shared.performUpload(url: fileAssetPath) { response in
if response {
// do stuff
}
}
to
UploadHelper.shared.performUpload(url: fileAssetPath) { [unowned self] response in
if response {
// do stuff
}
}

Do something when all alamofire requests are finished

I got an array of url and I want to make an alamofire request for each url in that array:
func getData(completion: #scaping(_success: Bool) -> Void) {
for url in self.myArray {
Alamofire.request(url).responseImage { response in
if let image = response.result.value {
print(image)
completion(true)
}
}
}
}
The problem is I can't know when ALL requests are finished, maybe because the for loop. Even using the completion handler.
If I try to do something on getData success, some requests are not finished yet.
I'd like to do something after all requests are done, like update a tableView
if you know how many urls you have (which you do) just keep a count of how often you reach the completion handler - something like this.
func getData(completion: #scaping(_success: Bool) -> Void) {
var urlCounter = self.myArray.count
for url in self.myArray {
Alamofire.request(url).responseImage { response in
if let image = response.result.value {
print(image)
completion(true)
urlCounter -= 1
if urlCounter == 0
{
// everything finished - do completion stuff
}
}
}
}
}