Control when a different number of requests are completed in Swift - swift

I have the following code that runs about :
for resFoto in resFotosResenhaEscolhidas {
jsonRequestUploadImagem = ResFotosModel.createJsonFotoResenha(resFoto)
let requestUploadImagem: NSMutableURLRequest = serviceRepository.clientURLRequest(wsUploadImagem, typeAutho: .basic, parms: "", body: jsonRequestUploadImagem as Dictionary<String, AnyObject>)
serviceRepository.post(requestUploadImagem, retryLogin: true, completion: {isOk,msgError,httpCode,needLogin,response in
self.checkResponseUploadImagemFotoResenha(response as AnyObject, httpCode)
})
}
func checkResponseUploadImagemFotoResenha(_ response:AnyObject, _ httpCode:Int) {
if httpCode != 200 {
let string = String(data: response as! Data, encoding: .utf8)
print( string!+" \n Erro HTTP: "+String(httpCode) )
} else {
// httpCode == 200
let data: Data = response as! Data // received from a network request, for example
let jsonResponse = try? JSONSerialization.jsonObject(with: data, options: [])
print("json response upload foto")
print(jsonResponse!)
}
}
The serviceRepository.post just run a "urlSession.dataTask", but I wanna know how can I control when the completion of the request.
The "resFotosResenhaEscolhidas" object contains 0 to 4 array inside it depending on the call. So, the code runs and create from 0 to 4 requests.
If the 4 requests are running, I just wanna know how can I check when they are finished?

Look at using a DispatchGroup. You'd create a DispatchGroup when you get ready to begin making network calls.
You'd call enter() on your dispatch group each time you begin a new URLSession task (or other async task.) You'd call leave() on the dispatch group in the completion handler for each task.
After you've submitted your async tasks, you'd call the dispatch group's notify() method to submit a block that will execute once all your async tasks are complete. (It's important that you wait until you've submitted your async tasks before calling notify(). If you try to call it before submitting your tasks, it invokes it's closure immediately since no tasks are running.)
I wrote a little demo project that uses a DispatchGroup to monitor a set of async tasks. (In the demo the tasks just delay for a random time before completing, and generate a random number.)
It waits until they've all completed, and then indicates the task that returned the largest value.
You can check it out on Github: DispatchGroupDemo on github

Related

Actors with shared state

The following two actors share the same URLSession instance. The actors work in isolation, so is this a problem because each actor may change properties on the session at the same time? How can I protect against this?
actor ImageDownloaderA {
let session = URLSession.shared
func fetchImage() async throws -> Data {
let request = ...
return try await session.data(for: ...)
}
}
actor ImageDownloaderB {
let session = URLSession.shared
func fetchImage() async throws -> Data {
let request = ...
return try await session.data(for: ...)
}
}
struct Fetcher {
func fetcher() async throws {
let downloaderA = ImageDownloaderA()
let downloaderB = ImageDownloaderB()
_ = try await downloaderA.fetchImage()
_ = try await downloaderB.fetchImage()
}
}
URLSession is Sendable and is safe to be passed across concurrency domains. The documentation also explicitly tells us that it is thread-safe:
Thread Safety
The URL session API is thread-safe. You can freely create sessions and
tasks in any thread context. When your delegate methods call the
provided completion handlers, the work is automatically scheduled on
the correct delegate queue.
It should be noted that these two requests are not actually executing concurrently. This is not because of URLSession, but rather because this code will await the completion of ImageDownloaderA.fetchImage before even starting ImageDownloaderB.fetchImage. If we want concurrent execution, we might use async let or a task group.
Now, you’re not showing us what you’re doing with these two responses, but let’s imagine that for demonstration purposes you wanted them to simply print the number of bytes that each returned.
func fetcher() async throws {
let downloaderA = ImageDownloaderA()
let downloaderB = ImageDownloaderB()
async let data1 = downloaderA.fetchImage()
async let data2 = downloaderB.fetchImage()
let count1 = try await data1.count
let count2 = try await data2.count
print(count1, count2)
}
That will run the requests concurrently because we used async let and then only introduced await suspension points after both had been started. For details on async let, see SE-0317.
But, regardless, you can use the same URLSession in separate concurrency contexts without incident.

Swift - Async/Await not stopping to let task finish

I'm writing a simple program designed to talk to a server, get some information, and display it. However, I'm running into issues with the async code, mainly that the code isn't stopping to allow the server to respond before continuing.
I know I have to be doing something wrong but have no idea what, any help is appreciated.
override func viewDidLoad() {
super.viewDidLoad()
Task{
let accessToken = await getToken()
}
print("Done")
}
private func getToken() async -> String{
let url = URL(string: "https://api.petfinder.com/v2/oauth2/token")
let payload = "grant_type=client_credentials&client_id=GUQj1MdQN3QunoxXz4vdd0DHPlcJC6yuqCLCEXavriJ4W6wTYV&client_secret=7whgSG3ZX6m9Cwfr2vEakOH90fSn3g0isIlae0CC".data(using: .utf8)
var request = URLRequest(url: url!)
request.httpMethod = "POST"
request.httpBody = payload
do{
let (data,_) = try await URLSession.shared.data(for: request)
let APItoken: token = try! JSONDecoder().decode(token.self, from: data)
return APItoken.access_token
}
catch{
return ""
}
}
If I understood the problem, you see "Done" being printed before the getToken() method is completed, right?
The problem is that print("Done") is outside of the Task.
When you call Task, it starts running what's in the closure and it immediately resumes after the closure, while your task is running in parallel, without blocking the main thread.
Place your print() inside the Task closure, right after the getToken() method, and you'll see that it'll be "Done" after you complete your POST request.
Task{
let accessToken = await getToken()
print("Done")
}
Await does not completely block your code, instead the thread that makes the call can be used to do something else, like get more user input, execute other task content that was waiting for a result and now has it. think like closure call backs, everything after the await, will be executed later, probable up to the enclosing task. Print(“done”) is not in your task, so it’s not required to wait for the code in the task execute, the task content is probable executing in another thread, as it doesn’t contain code that has to execute in the main thread.

How do I asynchronously initialize firebase firestore listeners while also knowing when all of the tasks are done?

Basically, at the launch of my app, I want to load the latest data from firebase from about 4-5 different documents. Then I also want to set up a listener to monitor data changes. I do this by calling 4-5 similar functions that take a dispatchGroup as an argument. I may be approaching this completely wrong but I could not think of any other way to do it. I just want to load those documents, set up listeners, and take certain action whenever those docs are loaded at the launch of the app.
// app launch
let dispatch = DispatchGroup()
getFirebaseDocument1(dispatch: dispatch)
getFirebaseDocument2(dispatch: dispatch)
getFirebaseDocument3(dispatch: dispatch)
getFirebaseDocument4(dispatch: dispatch)
getFirebaseDocument5(dispatch: dispatch)
dispatch.notify(queue:main) {
// execute some code to execute after all the documents are fetched
}
// typical getFirebaseDocument code
dispatch.enter()
let ref = someFirestoreReference
ref.addSnapshotListener { (snapshot, error) in
if let error = error{
// handle error
} else {
// load the document
}
dispatch.leave()
}
The code works fine when it's launched but crashes whenever the listener receives an update. I know this is because the dispatch.leave() is called in the listener function. However, I cannot seem to figure out a clever solution to where I can asynchronously load the data from firebase at launch while also setting up listeners. I would also prefer not to nest closures within one another as it wouldn't be asynchronous and it would also be a pain.
I may be wrong, but you should leave the group inside your block, here is the example how I do it using group
class func getRates(completion: #escaping EmptyBlock) {
let eurRequest = APIConfigs.request(part: "rs/price/history")
let usdRequest = APIConfigs.request(part: "rs/price/history/usd")
let group = DispatchGroup()
group.enter()
sendRequest(request: eurRequest, method: .get, parameters: ServerParameters.rates()) { response in
Course.current.addRate(rates: ratesRequest(response: response), type: .eur)
group.leave()
}
group.enter()
sendRequest(request: usdRequest, method: .get, parameters: ServerParameters.rates()) { response in
Course.current.addRate(rates: ratesRequest(response: response), type: .usd)
group.leave()
}
group.notify(queue: .main) {
completion()
}
}

Swift - Semaphore within semaphore [duplicate]

I'm entering the concurrency programming with some semaphore issues.
My function first loads data from server, analyze received info and then, if necessary, makes second request to server.
I tried different ways to make it run, none of them did it well.
My current code FOR ME seems to be correct, but on second request it just locks(maybe like a DeadLock) and the last log is "<__NSCFLocalDataTask: 0x7ff470c58c90>{ taskIdentifier: 2 } { suspended }"
Please, tell me what do I don't know. Maybe there is more elegant way to work with completions for these purposes?
Thank you in advance!
var users = [Int]()
let linkURL = URL.init(string: "https://bla bla")
let session = URLSession.shared()
let semaphore = DispatchSemaphore.init(value: 0)
let dataRequest = session.dataTask(with:linkURL!) { (data, response, error) in
let json = JSON (data: data!)
if (json["queue"]["numbers"].intValue>999) {
for i in 0...999 {
users.append(json["queue"]["values"][i].intValue)
}
for i in 1...lround(json["queue"]["numbers"].doubleValue/1000) {
let session2 = URLSession.shared()
let semaphore2 = DispatchSemaphore.init(value: 0)
let linkURL = URL.init(string: "https://bla bla")
let dataRequest2 = session2.dataTask(with:linkURL!) { (data, response, error) in
let json = JSON (data: data!)
print(i)
semaphore2.signal()
}
dataRequest2.resume()
semaphore2.wait(timeout: DispatchTime.distantFuture)
}
}
semaphore.signal()
}
dataRequest.resume()
semaphore.wait(timeout: DispatchTime.distantFuture)
P.S. Why do I do it. Server returns limited count of data. To get more, I have to use offset.
This is deadlocking because you are waiting for a semaphore on the URLSession's delegateQueue. The default delegate queue is not the main queue, but it is a serial background queue (i.e. an OperationQueue with a maxConcurrentOperationCount of 1). So your code is waiting for a semaphore on the same serial queue that is supposed to be signaling the semaphore.
The tactical fix is to make sure you're not calling wait on the same serial queue that the session's completion handlers are running on. There are two obvious fixes:
Do not use shared session (whose delegateQueue is a serial queue), but rather instantiate your own URLSession and specify its delegateQueue to be a concurrent OperationQueue that you create:
let queue = OperationQueue()
queue.name = "com.domain.app.networkqueue"
let configuration = URLSessionConfiguration.default()
let session = URLSession(configuration: configuration, delegate: nil, delegateQueue: queue)
Alternatively, you can solve this by dispatching the code with the semaphore off to some other queue, e.g.
let mainRequest = session.dataTask(with: mainUrl) { data, response, error in
// ...
DispatchQueue.global(attributes: .qosUserInitiated).async {
let semaphore = DispatchSemaphore(value: 0)
for i in 1 ... n {
let childUrl = URL(string: "https://blabla/\(i)")!
let childRequest = session.dataTask(with: childUrl) { data, response, error in
// ...
semaphore.signal()
}
childRequest.resume()
_ = semaphore.wait(timeout: .distantFuture)
}
}
}
mainRequest.resume()
For the sake of completeness, I'll note that you probably shouldn't be using semaphores to issue these requests at all, because you'll end up paying a material performance penalty for issuing a series of consecutive requests (plus you're blocking a thread, which is generally discouraged).
The refactoring of this code to do that is a little more considerable. It basically entails issuing a series of concurrent requests, perhaps use "download" tasks rather than "data" tasks to minimize memory impact, and then when all of the requests are done, piece it all together as needed at the end (triggered by either a Operation "completion" operation or dispatch group notification).

How to get data by URL in swift? [duplicate]

var arrayData: [String] = []
let bodyData = "parameter=test"
let URL: NSURL = NSURL(string: "Link to php file")
let request:NSMutableURLRequest = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "POST"
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue())
{
(response, data, error) in
var output = NSString(data: data, encoding: NSUTF8StringEncoding)
self.arrayData = self.JSONParseArray(output)
println(self.arrayData) //Print the result
}
println(self.arrayData) //Print nothing
It look like the new value is only available in sendAsynchronousRequest
Is there a way to make the new value accessible out of the sendAsynchronousRequest ?
sendAsynchronousRequest is, as the name suggests, asynchronous. So your final println runs before the request is complete. After the completion handler runs, the new data will be available for all readers (in or outside this handler).
sendAsynchronousRequest requires a callback passed as argument, which is executed once the asynchronous request has been completed. That's out of the linear flow in your code.
This is what happens:
you call sendAsynchronousRequest
the request is started, but the function returns immediately (it doesn't wait for the request to be completed)
the println line is executed next (last line in your code)
some time later, the asynchronous request is completed, and the closure is executed
the closure assigns a value to self.arrayData
If you want to use the updated value, you have to change the logic a little bit. You can call another closure or a class method (meaning an instance method, not a static one) to post process that variable once it has been set.