How to make multiple Alamofire requests with operation query - ios

I have an app that uploads multiple images to the server using Alamofire. Each image needs an upload token before uploading to the server. So, in a for loop, for every image file,
I make a get request via Alamofire to get the upload token
After getting the token, I make an upload request via Alamofire, with that token.
Here is my code:
func uploadFile(image: imageToUpload, onCompletion: #escaping ((Bool) -> Void)) {
...//some code
// Alamofire request to get an upload token
getUploadToken() { uploadToken in
if uploadToken != nil {
// Alamofire request to make the upload with uploadToken and image data
makeUploadRequest(token: uploadToken, image: imageToUpload) { uploadResponse in
onCompletion(uploadResponse)
}
}
}
And this is my for loop to iterate through the images to upload:
DispatchQueue.global(qos: .background).async {
let group = DispatchGroup()
for fileToUpload in filesToUpload {
group.enter()
FileOperations.shared.uploadFile(image: fileToUpload) { hasFinished in
if hasFinished {
group.leave()
}
}
group.wait()
}
}
So I need to use queues to implement this. My code needs to work like this:
Queue 1: getUploadToken request for image 1.
Queue 2: makeUploadRequest for image 1.
Queue 3: getUploadToken request for image 2.
Queue 4: makeUploadRequest for image 2.
... and so on.
So only after image 1 in for loop completes its whole process, the image 2 can start its process. With queues, even if the app is backgrounded, it should work.
How can I achieve this kind of behaviour with queues?
I can make this work with DispatchGroup(), but it doesn't work when the app backgrounded. I researched and read everything on the internet but I couldn't get anything seem to work. So I want to make this work with queues. Any help would be appreciated.

You are right, you can do this with an OperationQueue all you'll need to do is subclass Operation to get it configured to perform the actions you want and initialize an OperationQueue and configure that to run on a background thread and only allow one concurrent task. Then, add the operations to the queue in the order you want and they will begin executing.

Can you change your code to the following and check if for example three images are uploaded correctly.
let group = DispatchGroup()
DispatchQueue.global(qos: .background).async {
for fileToUpload in filesToUpload {
group.enter()
FileOperations.shared.uploadFile(image: fileToUpload) { hasFinished in
if hasFinished {
group.leave()
}
}
}
}
group.notify(queue: .main) {
print("Finished all upload requests.")
}

Related

How to get swift operation synced one after other using serial or concurrent queue in GCD?

I'm trying to fiddle with concurrent/serial queues and sync/async operations and came across a scenario which I'm not able to solve. Would be glad if someone can assist.
So it goes like this -
I have a queue and I'm trying to simulate an async image download operation by using asynAfter, and I'm able to get below result by this code.
var downloadQueue = DispatchQueue(label: "com.image.download", attributes: .concurrent)
var operation1 = {
downloadQueue.asyncAfter(deadline: DispatchTime.now() + 6.0) {
print("Image Download 1 Done")
}
}
var operation2 = {
downloadQueue.asyncAfter(deadline: DispatchTime.now() + 4.0) {
print("Image Download 2 Done")
}
}
var operation3 = {
downloadQueue.asyncAfter(deadline: DispatchTime.now() + 2.0) {
print("Image Download 3 Done")
}
}
operation1()
operation2()
operation3()
OUTPUT:
Image Download 3 Done //Prints after 2 seconds
Image Download 2 Done //Prints after 4 seconds
Image Download 1 Done //Prints after 6 seconds
Now the question arises if I want to get below 2 scenarios -
I want operation2 to start after my operation1 finishes, and operation3 to start after operation2 finishes. So that all operations are completed in combined (6.0+4.0+2.0) 12.0 seconds.
I want all operations to start simultaneously, but completions to trigger in order they were entered in queue. So that all operations are completed in combined 6.0 seconds.
I tried serial queue and concurrent queue with sync/async blocks, but everytime answer is same. Please guide.
Suppose you have an array with imageURLs that contains all image URL that you want to download.
let imageUrls = ["imageUrl1","imageUrl2","imageUrl3"]
Here is the block method that will process operation ex: image download in this case.
func getResult(url:String, completion: #escaping (Any?) -> Void) {
.... // THIS IS YOUR PROCESS THAT YOU WANT TO EXECUTE
// After completing process you got the image
completion(image)
}
In the completion block, you just pass the value that you have got (image in this case)
Now, this is the main process to use the getResult block method. Suppose you have a method like downloadAllImages that need imageUrls.
func downloadAllImages(imageUrls: [String]) {
var imageUrls = imageUrls
if imageUrls.count > 0 {
getResult(url: imageUrls[0]) { (data) in
// Now you are in the Main thread
// here data is the output got the output
imageUrls.remove(at: 0)// remove the down element
downloadAllImages(imageUrls: imageUrls) // Again call the next one
}
}
}
Hope you understand.
The point of asyncAfter(deadline:) is to submit a block to the queue at some point in the future. But what you're saying is that you want to submit a block now and have it block the queue until it has completed. First, if you want things to occur in order, then you want a serial queue (and in fact you almost always want a serial queue).
let downloadQueue = DispatchQueue(label: "com.image.download")
Then you're saying you have a task that you want to take 6 seconds, and block the queue until it's done. That's generally not something you should do, but for testing it's of course fine, and you'd use sleep to achieve it. (You should almost never use sleep, but again, for testing it's fine.)
let operation1 = {
Thread.sleep(forTimeInterval: 6)
print("Image Download 1 Done")
}
let operation2 = {
Thread.sleep(forTimeInterval: 4)
print("Image Download 2 Done")
}
let operation3 = {
Thread.sleep(forTimeInterval: 2)
print("Image Download 3 Done")
}
And submit them:
downloadQueue.async(execute: operation1)
downloadQueue.async(execute: operation2)
downloadQueue.async(execute: operation3)
If, alternately, you want these to run in parallel, you can use a concurrent queue instead.
For non-testing situations, there are other techniques you generally should use (most commonly DispatchGroup), but for something where you're just simulating something taking time, this is fine.
You said:
Now the question arises if I want to get below 2 scenarios -
I want operation2 to start after my operation1 finishes, and operation3 to start after operation2 finishes. So that all operations are completed in combined (6.0+4.0+2.0) 12.0 seconds.
First, this is a pattern generally to be avoided with network requests because you’re going to magnify network latency effects. You would only use this pattern where absolutely needed, e.g. if request 1 was a “sign in” operation; or, you needed something returned in the first request in order to prepare the subsequent request).
Often we’d do something simple, such as initiating the subsequent request in the completion handler of the first. Or, if you wanted a more flexible set of dependencies from a series of requests, you adopt a pattern that doesn’t use dispatch queues, e.g. you might create a custom, asynchronous Operation subclass that only completes when the network request is done (see point 3 in https://stackoverflow.com/a/57247869/1271826). Or if targeting recent OS versions, you might use Combine. There are a whole bunch of alternatives here. But you can’t just start a bunch of asynchronous tasks and have them run sequentially without one of these sorts of patterns.
I want all operations to start simultaneously, but completions to trigger in order they were entered in queue. So that all operations are completed in combined 6.0 seconds.
The whole idea of concurrent patterns is that you shouldn’t care about the order that they finish. So, use a structure that is not order-dependent, and then you can store the results as they come in. And use dispatch group to know when they’re all done.
But one thing at a time. First, how do you know when a bunch of concurrent requests are done? Dispatch groups. For example:
let group = DispatchGroup()
group.enter()
queue.asyncAfter(deadline: .now() + 6) {
defer { group.leave() }
print("Image Download 1 Done")
}
group.enter()
queue.asyncAfter(deadline: .now() + 4) {
defer { group.leave() }
print("Image Download 2 Done")
}
group.enter()
queue.asyncAfter(deadline: .now() + 2) {
defer { group.leave() }
print("Image Download 3 Done")
}
group.notify(queue: .main) {
// all three are done
}
Now, how do you take these requests, store the results, and retrieve them in the original order when you’re all done? First, create some structure that is independent of the order of the tasks. For example, let’s say you were downloading a bunch of images from URLs, then create a dictionary.
var images: [URL: UIImage] = [:]
Now fire off the requests concurrently:
for url in urls {
group.enter()
downloadImage(url) { result in
defer { group.leave() }
// do something with the result, e.g. store it in our `images` dictionary
switch result {
case .failure(let error): print(error)
case .success(let image): images[url] = image
}
}
}
// this will be called on main queue when they’re all done
group.notify(queue: .main) {
// if you want to pull them in the original order, just iterate through your array
for url in urls {
if let image = images[url] {
print("image \(url) has \(image.size)")
}
}
}
By the way, the above is using the following method to retrieve the images:
enum DownloadError: Error {
case unknown(Data?, URLResponse?)
}
#discardableResult
func downloadImage(_ url: URL, completion: #escaping (Result<UIImage, Error>) -> Void) -> URLSessionTask {
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard
let responseData = data,
let image = UIImage(data: responseData),
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode,
error == nil
else {
DispatchQueue.main.async {
completion(.failure(error ?? DownloadError.unknown(data, response)))
}
return
}
DispatchQueue.main.async {
completion(.success(image))
}
}
task.resume()
return task
}
The details here are not relevant. The key observation is that you should embrace concurrent patterns for determining when tasks are done (using dispatch groups, for example) and retrieving the results (store results in an unordered structure and access them in a manner that honors the order you intended).

Adding dependency of one BlockOperation on another is not working properly in swift

I have multiple api's in a controller and after successful response I have to reload the UITableView.
For now I started with two api having second api dependency on first one using BlockOperation and DispatchGroup in it.
First in viewDidLoad:
getDataFromAllApis {
self.tableView.reloadData()
}
Then I added the method:
func getDataFromAllApis(completion: #escaping (() -> Void)) {
let queue = OperationQueue()
let getFirstDataOperation = BlockOperation {
let group = DispatchGroup()
group.enter()
self.getFirstDataFromApi {
group.leave()
}
group.wait()
}
queue.addOperation(getFirstDataOperation)
let getSecondDataOperation = BlockOperation {
let group = DispatchGroup()
group.enter()
self.getSecondDataFromApi {
group.leave()
}
group.notify(queue: .main) {
completion()
}
}
queue.addOperation(getSecondDataOperation)
getSecondDataOperation.addDependency(getFirstDataOperation)
}
The problem that I am facing here is getSecondDataOperation executes first and returns to the tableview reload part.
Am I missing something here or there can be a different approach for it? Any help will be appreciated.
I have tried going through this post :
How can you use Dispatch Groups to wait to call multiple functions that depend on different data?
You are way overthinking this. Just call the second API from the completion handler of the first API. No operations, no dispatch groups, no nothing.
self.getFirstDataFromApi {
self.getSecondDataFromApi {
// call the completion handler
}
}
As for why your code didn't work, it's because you didn't do what the linked answer said to do!
How can you use Dispatch Groups to wait to call multiple functions that depend on different data?
It said to do this:
getSecondDataOperation.addDependency(getFirstDataOperation)
queue.addOperation(getFirstDataOperation)
queue.addOperation(getSecondDataOperation)
That isn't what you did. You did this:
queue.addOperation(getFirstDataOperation)
queue.addOperation(getSecondDataOperation)
getSecondDataOperation.addDependency(getFirstDataOperation) // too late
(However, that post, while ingenious, is not what I would do in this situation. If I wanted to sequentialize download operations, I would use the technique described here: https://fluffy.es/download-files-sequentially/. Or, in iOS 13, I'd use the Combine framework, as I describe here: https://stackoverflow.com/a/59889993/341994.)

Get the latest result from DispatchGroup wait

Problem Desctiption:
I want to do a bunch of asynchronous tasks by 'DispatchGroup' and when all of them finished it returned the result. In addition, I want to set timeout that limits the process and send me back the successful results by that time. I used the following structure:
Code Block
let myGroup = DispatchGroup()
var result = [Data]()
for i in 0 ..< 5 {
myGroup.enter()
Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
print("Finished request \(i)")
result.append(response.data)
myGroup.leave()
}
}
// Timeout for 10 seconds
myGroup.wait(timeout: DispatchTime(uptimeNanoseconds: 10000000000))
myGroup.notify(queue: .main) {
return result
}
How can I get the latest result if timeout happened?
Ok, so you are correctly using the enter/leave functionality of the DispatchGroup, but are having trouble with how to access the results of these. I think you are going wrong by trying to use both wait and notify, these two functions provide two different pieces of functionality not usually used together. After having setup up your work items, as you have done, you have two options:
The wait approach
This function blocks the calling queue and wait synchronously for either, the passed in wall time to elapse, or all work items in the group to leave. Because it is blocking the caller, it is important to always have a timeout in this function.
The notify approach
The function takes a target queue, and a block to be run when all work items in your group have completed. Here you are basically asking the system to notify you, asynchronously once all work items have been completed. Since this is asynchronous we are usually less worried about the timeout, it's not blocking anything.
Asynchronous wait (this appears to be what you want?)
If, as it seems you do, we want to be notified once all work items are complete, but also have a timeout, we have to do this ourselves, and it's not all that tricky. We can add a simple extension for the DispatchGroup class...
extension DispatchGroup {
func notifyWait(target: DispatchQueue, timeout: DispatchTime, handler: #escaping (() -> Void)) {
DispatchQueue.global(qos: .default).async {
_ = self.wait(timeout: timeout)
target.async {
handler()
}
}
}
}
This simple function dispatches asynchronously on a global background queue, then calls wait, which will wait for all work items to complete, or the specified timeout, whichever comes first. Then it will call back to your handler on the specified queue.
So that's the theory, how can you use this. We can keep your initial setup exactly the same
let myGroup = DispatchGroup()
var result = [Data]()
for i in 0 ..< 5 {
myGroup.enter()
Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
print("Finished request \(i)")
result.append(response.data)
myGroup.leave()
}
}
and then use our new function to wait for the end
myGroup.notifyWait(target: .main,
timeout: DispatchTime.now() + 10) {
// here you can access the `results` list, with any data that has
// been appended by the work items above before the timeout
// was reached
}

API calls blocks UI thread Swift

I need to sync web database in my coredata, for which I perform service api calls. I am using Alamofire with Swift 3. There are 23 api calls, giving nearly 24k rows in different coredata entities.
My problem: These api calls blocks UI for a minute, which is a long time for a user to wait.
I tried using DispatchQueue and performing the task in background thread, though nothing worked. This is how I tried :
let dataQueue = DispatchQueue.init(label: "com.app.dataSyncQueue")
dataQueue.async {
DataSyncController().performStateSyncAPICall()
DataSyncController().performRegionSyncAPICall()
DataSyncController().performStateRegionSyncAPICall()
DataSyncController().performBuildingRegionSyncAPICall()
PriceSyncController().performBasicPriceSyncAPICall()
PriceSyncController().performHeightCostSyncAPICall()
// Apis which will be used in later screens are called in background
self.performSelector(inBackground: #selector(self.performBackgroundTask), with: nil)
}
An API call from DataSyncController:
func performStateSyncAPICall() -> Void {
DataSyncRequestManager.fetchStatesDataWithCompletionBlock {
success, response, error in
self.apiManager.didStatesApiComplete = true
}
}
DataSyncRequestManager Code:
static func fetchStatesDataWithCompletionBlock(block:#escaping requestCompletionBlock) {
if appDelegate.isNetworkAvailable {
Util.setAPIStatus(key: kStateApiStatus, with: kInProgress)
DataSyncingInterface().performStateSyncingWith(request:DataSyncRequest().createStateSyncingRequest() , withCompletionBlock: block)
} else {
//TODO: show network failure error
}
}
DataSyncingInterface Code:
func performStateSyncingWith(request:Request, withCompletionBlock block:#escaping requestCompletionBlock)
{
self.interfaceBlock = block
let apiurl = NetworkHttpClient.getBaseUrl() + request.urlPath!
Alamofire.request(apiurl, parameters: request.getParams(), encoding: URLEncoding.default).responseJSON { response in
guard response.result.isSuccess else {
block(false, "error", nil )
return
}
guard let responseValue = response.result.value else {
block (false, "error", nil)
return
}
block(true, responseValue, nil)
}
}
I know many similar questions have been already posted on Stackoverflow and mostly it is suggested to use GCD or Operation Queue, though trying DispatchQueues didn't work for me.
Am I doing something wrong?
How can I not block UI and perform the api calls simultaneously?
You can do this to run on a background thread:
DispatchQueue.global(qos: .background).async {
// Do any processing you want.
DispatchQueue.main.async {
// Go back to the main thread to update the UI.
}
}
DispatchQueue manages the execution of work items. Each work item submitted to a queue is processed on a pool of threads managed by the system.
I usually use NSOperationQueue with Alamofire, but the concepts are similar. When you set up an async queue, you allow work to be performed independently of the main (UI) thread, so that your app doesn't freeze (refuse user input). The work will still take however long it takes, but your program doesn't block while waiting to finish.
You really have only put one item into the queue.
You are adding to the queue only once, so all those "perform" calls wait for the previous one to finish. If it is safe to run them concurrently, you need to add each of them to the queue separately. There's more than one way to do this, but the bottom line is each time you call .async {} you are adding one item to the queue.
dataQueue.async {
DataSyncController().performStateSyncAPICall()
}
dataQueue.async {
DataSyncController(). performRegionSyncAPICall l()
}

iOS best way to download bulk images

In my app, I have to download nearly 500 images as I open a ViewController. Downloading all 500 images at a time is not a right idea. I'd like to keep 5 active asynchronous downloads at a time. When any one in five is completed, it should start the next.
I also have a refresh control which will restart downloading all the images from first.
Which technique I could go for to implement this modal?
Here is what I tried so far,
Semaphore is created in property declaration
private var semaphore = dispatch_semaphore_create(5)
After getting web service response,
private func startDownloadingImages() {
for place in places {
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER)
self.downloadImageForPlace(place)
}
}
private func downloadImageForPlace(place: Place) {
ApplicationControls.getImageForPlace(place, withCompletion: { (image, error) -> () in
// error checks
dispatch_async(dispatch_get_main_queue(), {
// UI update
dispatch_semaphore_signal(self.semaphore)
})
})
}
But when I tap refresh control, app locks at dispatch_semaphore_wait and I could able to find a way to reset semaphore.
I would use an OperationQueue like this
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 5;
for url in imageUrls {
queue.addOperationWithBlock { () -> Void in
let img1 = Downloader.downloadImageWithURL(url)
NSOperationQueue.mainQueue().addOperationWithBlock({
//display the image or whatever
})
}
}
you can stop your operations with this
queue.cancelAllOperations();
and then just restart the whole thing.
The only thing that you have to change is that your requests have to be synchronous then. Because this approach wont work with callbacks.

Resources