How to run multiple API call simultaneously using async/await in swift? - ios

As per the new Structured Concurrency in Swift, we can await a API call which is done in an async context,
But I want to make multiple API calls simultaneously,
for example, currently If add the API calls one after another,
profileData = await viewModel.getProfileData()
userCardData = await viewModel.getCardDetails()
The problem in the above case is CardDetails is fetched only after fetching the profile data.

It can be achieved using the async let.
This special syntax allows you to group several asynchronous calls and await them together,
Example,
async let profileData = viewModel.getProfileData()
async let userCardData = viewModel.getCardDetails()
The above statement indicated that profileData and userCardData, will be available at some point of time in future, just like a promise.
Now in order to use profileData and userCardData we need to use await, incase the data is fetched and available it would return immediately, or it suspend until the data is available.
The value from the above request can be used like,
let (fetchedProfileData, fetchedUserCardData) = try await (profileData, userCardData)

Related

Aptos SDK - multiple payloads in one transaction

Sending one payload is easy, for example:
let payload1 = txBuilder.buildTransactionPayload("0x1::aptos_account::transfer",[],[to.address(),100000])
let raw = await client.generateRawTransaction(acc.address(),payload1)
let signed = await client.signTransaction(acc,raw);
let res = await client.submitTransaction(signed);
console.log(res)
let waiting = await client.waitForTransaction(res.hash);
But how do I send more than one payload in a transaction? is it possible in Aptos?
This is not possible, you can only send one payload per transaction. However, if your goal is to do multiple things in a single transaction, that is possible with Move scripts. These posts explain that approach in more detail:
How do I execute a Move script with the Aptos CLI?
How do I execute a Move script on Aptos using the Rust SDK?

Swift Siesta Framework: do something before sending requests

I am trying the Siesta framework and I want to call a function before sending every API calls.
I saw that decorateRequests(with:) is best suited for what I am looking to do, but as the return value must be a Request, there's an error on the following code:
service.decorateRequests(with: { (res, req) -> Request in
if (res.url == self.tests.url) {
// do things..., then call req.repeated()
} else {
req.repeated()
}
})
However, I have this error:
Missing return in a closure expected to return 'Request'
Any idea how I can make it work? Thanks
The basic Swift syntax error here is that you need to use the return keyword if a value-returning closure contains more than one statement.
If what you need to do is something that either:
can synchronously block the main thread because it is brief (e.g. log the request, flip a test expectation), or
needs to be started when the request is sent, but the request can go ahead and start immediately without waiting for it to finish
…then it needn’t be complicated. Do your brief task while everyone waits, then return the request:
service.decorateRequests { req, res in
if res.url == self.tests.url {
doThatOtherThing() // blocks the main thread, which is fine if it’s brief
}
return req
}
If on the other hand you need to do something that will take an indefinite amount of time while the main thread continues, and then at a later time initiate that request, then Siesta currently doesn’t support that very well. You can do it by writing a custom implementation of the Request protocol, but that's laborious and error prone. A better approach is coming in a future version of Siesta.

How to guarantee the correct order in async request

I have a iOS application that use Alamofire to make URL requests. I sometimes find the requests arriving in the wrong order when there is very little time between them. This has to do with the nature of async requests as I understand. Is there any way to guarantee the correct order of the requests? I've been thinking you could wait for each request to finish because you have a completion handler or maybe you could handle this at the server side with a timestamp on each request so the server can discard requests that has a lower timestamp. I don't know what's the best solution though.
My code so far:
Alamofire.request(
defaults.string(forKey: "APIurl")! + path,
method: httpMethod,
parameters: parameters,
encoding: JSONEncoding.default,
headers: headers).responseJSON
{ response in
// Check if the request was successful
var success = false
if (response.result.isSuccess) {
let statusCode = response.response!.statusCode
if (statusCode == 200) {
success = true
} else {
showAlert("COULD_NOT_COMPLETE_TASK_TITLE", message: "TRY_AGAIN_LATER")
}
}
}
I use sliders to change a value between 0 and 100. So in my case the order of the requests are crucial. Let's say I change a slider from 50 to 60. With async requests it sometimes execute first 60 and then 50. This is an issue as it's sent to my API that way and saves the latest value (in this case 50) in the database even though my desired value was 60.
If the thread is serial which in your case it is then the order will always be the same as you input it. So calling a few operations asynchronously on a same serial thread will force the operations to preserve that order.
The problem in your case is that you are not calling these operations, Alamofire is calling them. The order is preserved but it depends on when the responses are received and parsed. That means you may have no control over the order of the async operations being called.
You have 2 ways of serializing responses:
You need to wait for each response to be completed before you call the next request. If your responses are standard (all look alike) you only need some manager that will store an array of requests and will not call the new one until the previous one is completed. This might be a bit slow since there is no reason (or at least it seems that way in your case) not to perform the requests at the same time.
Serialize the responses so they are called in the same order as input. This means you call the requests whenever and responses will be called whenever. But once the response is received you will check for other responses being complete and only if they are you will trigger a callback on this one. That would again mean having some manager that serializes there responses.
So for the second one you would need something like:
SerializedRequestManager.performRequest(request, myCallbackClosure)
The manager would then save the request into some array of request wrappers like:
let requestWrapper = RequestWrapper(request, myCallbackClosure, isCompleted: false)
self.requestPool.append(requestWrapper)
AF.performRequest(request, myInternalClosure)
Then on response (myInternalClosure) you need to set the correct wrapper to have response to true and then flush the responses from the start of the array. All the responses finished then must be removed from the array:
requestWrapper.responseData = data // Same wrapper that was just added into the array
requestWrapper.responseError = error // Same wrapper that was just added into the array
requestWrapper.isCompleted = true
self.flushResponses()
So then flushResponses:
var newPool = [RequestWrapper]() // This is where all the pending items will stay
var shouldFlushResponse = true // Will be set to false with first request that was not completed
self.requestPool.forEach { wrapper in
if wrapper.isCompleted && shouldFlushResponse {
wrapper.callback(wrapper.responseData, wrapper.responseError) // perform response closure
} else {
shouldFlushResponse = false
newPool.append(wrapper)
}
}
self.requestPool = newPool
But you need to be very careful about the multithreading here. All the operation on the array requestPool should be done on the same thread but it may be any thread you want.
Well if the order of requests in crucial in your case then you should go for the NSOperationQueue that is the only way to make sure the order of your requests.
Follow this tutorial to have a border idea

swift, calling network another time in the same task's thread

Sorry for beginner's question.
I have an action that depends on the result of the data returned from the network, and the action may require another network request. Since the first network request is called in datatask already, I want to use that same thread for the 2nd network request, but don't know how to do it. Any tip? tks
let task = URLSession.shared.dataTask(with: request as URLRequest )
{ data, response, error in
if func_1 (data) {
return
}else {
//call another network request here, but don't want to do
//another task = URLSession.shared.... again since we are already on an async thread
}
}
//call another network request here, but don't want to do
//another task = URLSession.shared.... again since we are already on an async thread
This is a misunderstanding. There is no problem creating another network request at this point. All that does is place another request on the appropriate queue. It doesn't matter what thread you're running on when you schedule that.
I highly recommend Apple's Concurrency Programming Guide. You may have some misunderstandings about how iOS concurrency works. A key part of iOS work is understanding GCD queues, and particularly how they differ from threads.

Alamofire cancel sometimes does not work

I have a simple request like:
func newRequest() {
println("CANCEL = \(self.getTime())")
self.request_.cancel()
self.request_ = request(method, url)
validate(statusCode: [200])
.validate(contentType: ["application/json"])
.responseJSON { [unowned self] (_, _, json, error) in
if(error?.code == NSURLErrorCancelled ) {
println("CANCELED!")
}
println("DONE LOADING = \(self.getTime())")
// ...
}
}
As shown above, when new request is invoked, I want previous to be canceled.
And it usually works, but sometimes when previous request is about to end (there is a very short amount of time between logs), it does not.
(newRequest) CANCEL = 1436103465.93128
// CANCELED! SHOULD BE HERE
(previousRequest) DONE LOADING = 1436103466.08223
To make it work I added a var isCanceled and check whether it is set to true.
I am not sure if it works as it should (it may be too late to cancel) or it is a small bug.
You expectation here is incorrect. The cancellation is not a synchronous behavior. It is asynchronous and needs to hop several dispatch queues before your responseJSON closure will be called. Since you are repointing the self.request_ reference to a new request, your previous request is actually going out of memory and your responseJSON closure for the previous request will never be executed.
Instead of using a single reference to one request, you could use your self.request_ property to store the latest request, and use a requests set to store all the active requests. Once the responseJSON closure is called, make sure to remove the request from the requests set. This way, you would keep a reference to all requests until they finished properly canceling.
Believe me, Alamofire cancellation works properly. Our giant test suite agrees. 👍🏼

Resources