RxSwift and Moya synchronous request - swift

Right now i'm having a situation where i'm starting several request from separated threads, something like a Thread Pool.
I want to make that requests to be synchronous, so they will be started and ended on exactly the same thread, not the Main thread.
I tried to set the .observeOn(CurrentThreadScheduler.instance), but as the response handler thread was always Main thread instead of a current.
Is it possible to specify the current thread as a handler thread for the response?

Is it possible to specify the current thread as a handler thread for
the response?
There is a way! Take a look at MoyaProvider class.
/// Request provider class. Requests should be made through this class only.
open class MoyaProvider<Target: TargetType>: MoyaProviderType {
/// Propagated to Alamofire as callback queue. If nil - the Alamofire default (as of their API in 2017 - the main queue) will be used.
let callbackQueue: DispatchQueue?
/// Initializes a provider.
public init(..., callbackQueue: DispatchQueue? = nil, ...) {
...
self.callbackQueue = callbackQueue
}
}
You are allowed to provide a callback queue. Just provide the desired queue.

Related

Calling a method from an actor's init method

I'm trying to convert one of my classes in Swift to an actor. The current init method of my class calls another instance method to do a bunch of initialization work. After converting, here's a simplified version of my actor:
actor MyClass {
private let name: String
init(name: String) {
self.name = name
self.initialize() // Error on this line
}
private func initialize() {
// Do some work
}
func login() {
self.initialize()
// Do some work
}
// Bunch of other methods
}
I get the following error when I try to compile:
Actor-isolated instance method 'initialize()' can not be referenced from a non-isolated context; this is an error in Swift 6
I found that I can replace self.initialize() with:
Task { await self.initialize() }
Is that the best way to do this? Could this cause any race conditions where an external caller can execute a method on my actor before the initialize() method has a chance to run? Seems cumbersome that you are not in the isolated context in the actor's init method. I wasn't able to find any Swift documentation that explained this.
I wasn't able to find any Swift documentation that explained this.
This is explained in the proposal SE-0327, in this section (emphasis mine):
An actor's executor serves as the arbiter for race-free access to the
actor's stored properties, analogous to a lock. A task can access an
actor's isolated state if it is running on the actor's executor. The
process of gaining access to an executor can only be done
asynchronously from a task, as blocking a thread to wait for access is
against the ethos of Swift Concurrency. This is why invoking a
non-async method of an actor instance, from outside of the actor's
isolation domain, requires an await to mark the possible suspension.
The process of gaining access to an actor's executor will be referred
to as "hopping" onto the executor throughout this proposal.
Non-async initializers and all deinitializers of an actor cannot hop
to an actor's executor, which would protect its state from concurrent
access by other tasks. Without performing a hop, a race between a new
task and the code appearing in an init can happen:
actor Clicker {
var count: Int
func click() { self.count += 1 }
init(bad: Void) {
self.count = 0
// no actor hop happens, because non-async init.
Task { await self.click() }
self.click() // đź’Ą this mutation races with the task!
print(self.count) // đź’Ą Can print 1 or 2!
}
}
To prevent the race above in init(bad:), Swift 5.5 imposed a restriction on what can be done with self in a non-async initializer. In particular, having self escape in a closure capture, or be passed (implicitly) in the method call to click, triggers a warning that such uses of self will be an error in Swift 6.
They then went on to give another example that should have been accepted by the compiler, and suggested to loosen those restriction. In either case though, it does not apply to your code. See this section if you want the details about why it still does not compile in newer versions of Swift.
So the solution is, as the first quote says, mark the init as async. This way the initialiser would be effectively run on the actor's executor. The caller would use await when initialising, to "hop" in.

How to use URLSession in MainActor

I have a ViewModel that is isolated with #MainActor and I assume that every part of this ViewModel class should run on the main actor (main thread), right?
So the question is, what about the async call of URLSession.shared.data?
Does it also run on main thread? Isn't that a bad approach?
#MainActor
class ViewModel {
#Published var name: String = ""
func fetchName() async {
guard let (data, _) = try? await URLSession.shared.data(from: URL(string: "http://....")!),
let response = try? JSONDecoder().decode(Response.self, from: data) else {
return
}
self.name = response.name
}
}
Does it also run on main thread
No. That's the whole point of saying await. At that moment, the system can switch contexts to a background thread without you knowing or caring. Moreover, at await your code pauses without blocking — meaning that while you're waiting for the download, the main thread is free to do other work, the user can interact with your app, etc. Don't worry be happy.
The issue is not the URLSession code that you await. That runs on a separate thread managed by URLSession. Whenever you see await, that means that the current execution is suspended while the asynchronous task runs, and that the current actor is free to run other code. All is well.
The only potential concern is code that runs synchronously on the main actor (i.e., everything besides what we await). In this case, the only relevant portion is the code after the await, inside what is called the “continuation”.
In this case, the continuation consists of the decode of the JSON and the updating of the property. That is modest and you are generally fine running that on the main actor.
But if the continuation consisted of anything anything more substantial (e.g. decoding many megabytes of JSON or decoding an image or the like), then you would want to move that off the main actor. But in this case, you are fine.
For more information, see WWDC 2021 video Swift concurrency: Behind the scenes.

NS_SWIFT_UI_ACTOR annotation not working with async variants of callback based objective-C methods

In Objective-c we can use NS_SWIFT_UI_ACTOR to indicate that a method or a whole class should be executed on the main actor (as we would do with #MainActor in Swift). This works correctly since, when invoking a method which returns a value from outside the main actor (using a Task.detached with background priority), the compiler forces us to call it using "await" (to allow for "actor switch") and, at runtime, I can see that the code is executed on the main thread.
However, when calling a callback based method on the same class (which gets an automatically "imported" async variant, as explained here) the NS_SWIFT_UI_ACTOR seems not working properly, and the code is executed on a background thread.
NS_SWIFT_UI_ACTOR
#interface ObjcClass : NSObject
- (NSString *)method1;
- (void)method2WithCompletion: (void (^)(NSString * _Nonnull))completion;
#end
Task.detached(priority: .background) {
let objcInstance = ObjcClass()
print(await objcInstance.method1()) <----- this is called on the main thread
print(await objcInstance.method2()) <----- this is called on a bg thread
}
While I understand what your expectation is I don't think as of now the way actor is implemented there is any way to resolve this. Actors in swift are reentrant, which means whenever you invoke any async method inside actor's method/actor task, actor suspends the current task until the method returns and starts processing other tasks. This reentrancy behavior is important as it doesn't just lock the actor when executing a long-running task.
Let's take an example of a MainActor isolated async method:
func test() async {
callSync() // Runs on MainActor
await callAsync() // Suspends to process other task, waits for return to resume executing on MainActor
callSync() // Runs on MainActor
}
But since swift can't infer from an objective c async method which part should run on MainActor and for which part actor can just suspend the current task to process other tasks, the entire async method isn't isolated to the actor.
However, there are proposals currently to allow customizing an actor's reentrancy so you might be able to execute the entire objc async method on the actor.

How can I avoid that my Swift async method runs on the main thread in SwiftUI?

I watched all the videos on async/await (and actors), but I am still confused a bit.
So assume I have an async method: func postMessage(_ message: String) async throws and I have a simple SwiftUI view.
#MainActor
struct ContentView: View {
#StateObject private var api = API()
var body: some View {
Button("Post Message") {
Task {
do {
try await api.postMessage("First post!")
} catch {
print(error)
}
}
}
}
}
Here I explicitly tell SwiftUI to use the #MainActor although I know it would have inferred from #StateObject.
To my understanding, since we use the #MainActor the work is done on the main thread. Meaning the work on Task would also be done on the main thread. Which is not what I want as the uploading process might take some while. In this case I could use Task.detached to use a different thread. Which would solve it. If my understanding is correct.
Now to make it a little more complicated. What if... postMessage would return a post identifier as an integer and I like to present that in the view?
struct ContentView: View {
#StateObject private var api = API()
#State private var identifier: Int = 0
var body: some View {
Text("Post Identifier: \(String(describing: identifier))")
Button("Post Message") {
Task {
do {
identifier = try await api.postMessage("First post!")
} catch {
identifier = 0
print(error)
}
}
}
}
}
This would work as (again to my understanding) Task is run on the main thread. If I would change it now to Task.detached we will get an error "Property 'identifier' isolated to global actor 'MainActor' can not be mutated from a non-isolated context".
Which makes sense, but how can we return the value to the main actor so the view can be updated?
Perhaps my assumptions are wrong. Let's look at my API class.
actor API {
func postMessage(_ message: String) async throws -> Int {
// Some complex internet code
return 0
}
}
Since the API runs in its own actor. Would the internet work also run on a different thread?
The question is very good, and answer is complex. I have spent significant time on this topic, for detailed information please follow the link to Apple developer forum in comments.
All tasks mentioned below are unstructured tasks, eg. created by Task ...
This is key: "The main actor is an actor that represents the main thread. The main actor performs all of its synchronization through the main dispatch queue.
Unstructured tasks created by Task.init eg. Task { ... } inherit the actor async context.
Detached tasks Task.detached, async let = tasks, group tasks do NOT inherit actor async context.
Example 1: let task1 = Task { () -> Void in ... } creates and starts new task that inherits priority and async context from the point, where it is called. When created on the main thread, the task will run on the main thread.
Example 2: let task1 = Task.detached { () -> Void in ... } creates and starts new task that does NOT inherit priority nor async context. The task will run on some thread, very likely on other thread than is the current thread. The executor decides that.
Example 3: let task1 = Task.detached { #MainActor () -> Void in ... } creates and starts new task that does NOT inherit priority nor async context, but the task will run on the main thread, because it is annotated so.
Very likely, the task will contain at least one await or async let = command. These commands are part of structured concurrency, and you cannot influence, on what thread are the implicitly created tasks (not discussed here at all) executed. The Swift executor decides that.
The inherited actor async context has nothing to do with threads, after each await the thread may change, however the actor async context is kept throughout all the unstructured task (yes, may be on various threads, but this is important just for the executor).
If the inherited actor async context is MainActor, then the task runs on the main thread, from beginning till its end, because the actor context is MainActor. This is important if you plan to run some really parallel computation - make sure all the unstructured tasks do not run on the same thread.
ContentView is on #MainActor in both the cases: in the first case is it #MainActor explicitely, the second case uses #StateObject property wrapper that is #MainActor, so the whole ContentView structure is #MainActor inferred. https://www.hackingwithswift.com/quick-start/concurrency/understanding-how-global-actor-inference-works
async let = is a structured concurrency, it does not inherit actor async context, and runs in parallel immediately, as scheduled by the executor (on some other thread, if available)
The example on top has one system flaw: #StateObject private var api = API() is #MainActor. This is forced by the #StateObject. So, I would recommend to inject other actor with other actor async context as a dependency without #StateObject. The async/await will really work, keeping await calls with the proper actor contexts.
You said:
To my understanding, since we use the #MainActor the work is done on the main thread. Meaning the work on Task would also be done on the main thread. Which is not what I want as the uploading process might take some while.
Just because the uploading process (a) might take some time; and (b) was invoked from the main actor, that does not mean that the main thread will be blocked while the request is performed.
In fact, this is the whole point of Swift concurrency’s await. It frees the current thread to go do other things while we await results from the called routine. Do not conflate the await of Swift concurrency (which will not block the calling thread) with the various wait constructs of traditional GCD patterns (which will).
E.g., consider the following code launched from the main actor:
Task {
do {
identifier = try await api.postMessage("First post!")
} catch {
identifier = 0
print(error)
}
}
Yes, because you used Task and invoked it from the main actor, that code will also run on the main actor. Thus, identifier will be updated on the main actor. But it says nothing about which actor/queue/thread that postMessage is using. The await keyword means “I'll suspend this path of execution, and let the main thread and go do other things while we await the postMessage initiating its network request and its eventual response.”
You asked:
Let's look at my API class.
actor API {
func postMessage(_ message: String) async throws -> Int {
// Some complex internet code
return 0
}
}
Since the API runs in its own actor. Would the internet work also run
on a different thread?
As a general rule, networking libraries perform their requests asynchronously, not blocking the threads from which they are called. Assuming the “complex internet code” follows this standard convention, you then you simply do not have to worry about what thread it is running on. The actor’s thread will not be blocked while the request runs.
Taking this a step further, the fact that the API is its own actor is immaterial to the question. Even if API was on the main actor, because the network request runs asynchronously, whatever the thread used by API will not be blocked.
(NB: The above assumes that the “complex internet code” has followed conventional asynchronous network programming patterns. We obviously cannot comment further without seeing a representative example of this code.)
See WWDC 2021 video Swift concurrency: Behind the scenes if you are interested in how Swift concurrency manages threads for us.
The Task is the bridge between the Async and the Synchronous world. The completion block of Button being the synchronous world and the postMessage method as being the asynchronous world.
The Task is run on the #MainActor however since we await the result of api.postMessage it may suspend and the main thread is not blocked.
Thanks to Quang HĂ  and Lorem Ipsum.
Useful information regarding this question from WWDC.
From Discover concurrency in SwiftUI (Timecode - 7:24)
To update correctly, SwiftUI needs these events to happen in order: objectWillChange, the ObservableObject’s state is updated, and then the run loop reaches its next tick. If I can ensure that these all happen on the main actor, I can guarantee this ordering. Prior to Swift 5.5, I might have dispatched back to the main queue to update my state, but now it’s much easier. Just use await! By using await to make an async call from the main actor, I let other work continue on the main actor while the async work happens. This is called “yielding” the main actor.
From Meet async/await in Swift (Timecode - 22:13):
The solution is to use the async task function. An async task packages up the work in the closure and sends it to the system for immediate execution on the next available thread, like the async function on a global dispatch queue.
From Protect mutable state with Swift actors (Timecode - 7:25)
If the actor is busy, then your code will suspend so that the CPU you're running on can do other useful work. When the actor becomes free again, it will wake up your code -- resuming execution -- so the call can run on the actor. The await keyword in this example indicates that the asynchronous call to the actor might involve such a suspension.
From Protect mutable state with Swift actors (Timecode - 23:47)
When you are building an app, you need to think about the main thread. It is where the core user interface rendering happens, as well as where user interaction events are processed. Operations that work with the UI generally need to be performed from the main thread. However, you don't want to do all of your work on the main thread. If you do too much work on the main thread, say, because you have some slow input/output operation or blocking interaction with a server, your UI will freeze. So, you need to be careful to do work on the main thread when it interacts with the UI but get off the main thread quickly for computationally expensive or long-waiting operations. So, we do work off the main thread when we can and then call DispatchQueue.main.async in your code whenever you have a particular operation that must be executed on the main thread.
If you know you're already running on the main thread, you can safely access and update your UI state. If you aren't running on the main thread, you need to interact with it asynchronously. This is exactly how actors work.
The main actor is an actor that represents the main thread. It differs from a normal actor. The main actor performs all of its synchronization through the main dispatch queue. This means that, from a runtime perspective, the main actor is interchangeable with using DispatchQueue.main.

What happens when I set an object in an asychronous callback?

From the Alamofire page,
Rather than blocking execution to wait for a response from the server, a callback is specified to handle the response once it's received. The result of a request is only available inside the scope of a response handler. Any execution contingent on the response or data received from the server must be done within a handler.
I understand that but what actually happens when you try to set your variable?
For example:
class Person: NSObject {}
var person = Person()
Alamofire
.request(APIRouter.GetUpComingRides(parameters))
.responseJSON { response in
// Doesn't do anything
self.instanceVariable = response.result.value
}
}
Using unsafeAddressOf, I know that response.result.value actually have an address in memory. So, what happens when you set your variable to that?
If I use a value type instead of a reference type, it will work.
struct Person {}
var person: Person
Alamofire
.request(APIRouter.GetUpComingRides(parameters))
.responseJSON { response in
// Actually sets the result to the value
self.result = response.result.value
}
}
Reading the NSURLSession document, dataTaskWithRequest which is what Alamofire calls, does the operation on the delegate queue. There must be something that disables setting the pointer of my instance variable object to an object in a delegate queue.
EDIT: For more clarification: See How to return value from Alamofire
You actually don't have to use the completion handler pattern if you use value types (structs, strings, int, etc...)
I think you have misunderstood what "only available inside the scope of a response handler" means in this context. There is no mechanism preventing you from passing or copying a value out of the response block.
Alamofire is trying to explain that it provides no synchronous interface to this request's response value and therefore you must use the async handler to trigger your use of the response. You're free to copy the response value out of the response handler but the variable you're setting will not be set until the response handler executes some time in the future.
The Doesn't do anything comment in the question above is probably an example of this. It's not that the variable is not set but rather than some other branch of code is checking the value of that variable before the completion handler has executed and finding it to still be nil.