I have noticed that the start(queue:) method in NWPathMonitor requires a queue of type DispatchQueue. Is there a way to implement this using Swift Modern Concurrency, probably using AsyncStream?
Using Apple documentation for AsyncStream, I have created the extension to NWPathMonitor, but I cannot start the NWPathMonitor monitor, any suggestion will be appreciated, thanks
extension NWPathMonitor {
static var nwpath: AsyncStream<NWPath> {
AsyncStream { continuation in
let monitor = NWPathMonitor()
monitor.pathUpdateHandler = { path in
continuation.yield(path)
}
continuation.onTermination = { #Sendable _ in
monitor.cancel()
}
// monitor.start(queue: )
}
}
}
Read Apple's documentation
If you are wrapping legacy APIs within some continuation pattern (whether with AsyncStream or withCheckedContinuation or whatever), that wrapped code will have to employ whatever pattern the legacy API requires.
So, in short, if an API wrapped by an AsyncStream requires a dispatch queue, then simply supply it a queue.
So:
extension NWPathMonitor {
func paths() -> AsyncStream<NWPath> {
AsyncStream { continuation in
pathUpdateHandler = { path in
continuation.yield(path)
}
continuation.onTermination = { [weak self] _ in
self?.cancel()
}
start(queue: DispatchQueue(label: "NSPathMonitor.paths"))
}
}
}
Then you can do things like:
func startMonitoring() async {
let monitor = NWPathMonitor()
for await path in monitor.paths() {
print(path.debugDescription)
}
}
A few unrelated and stylistic recommendations, which I integrated in the above:
I did not make this static, as we generally want our extensions to be as flexible as possible. If this is in an extension, we want the application developer to create whatever NWPathMonitor they want (e.g., perhaps requiring or prohibiting certain interfaces) and then create the asynchronous sequence for the updates for whatever path monitor they want.
I made this a function, rather than a computed property, so that it is intuitive to an application developer that this will create a new sequence every time you call it. I would advise against hiding factories behind computed properties.
The concern with a computed property is that it is not at all obvious to an application developer unfamiliar with the underlying implementation that if you access the same property twice that you will get two completely different objects. Using a method makes this a little more explicit.
Obviously, you are free to do whatever you want regarding these two observations, but I at least wanted to explain my rationale for the adjustments in the above code.
Related
I had an issue in Swift 5.5 and I don't really understand the solution.
import Foundation
func testAsync() async {
var animal = "Dog"
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
animal = "Cat"
print(animal)
}
print(animal)
}
Task {
await testAsync()
}
This piece of code results in an error
Mutation of captured var 'animal' in concurrently-executing code
However, if you move the animal variable away from the context of this async function,
import Foundation
var animal = "Dog"
func testAsync() async {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
animal = "Cat"
print(animal)
}
print(animal)
}
Task {
await testAsync()
}
it will compile. I understand this error is to prevent data races but why does moving the variable make it safe?
Regarding the behavior of the globals example, I might refer you to Rob Napier’s comment re bugs/limitations related to the sendability of globals:
The compiler has many limitations in how it can reason about global variables. The short answer is “don't make global mutable variables.” It‘s come up on the forums, but hasn‘t gotten any discussion. https://forums.swift.org/t/sendability-checking-for-global-variables/56515
FWIW, if you put this in an actual app and change the “Strict Concurrency Checking” build setting to “Complete” you do receive the appropriate warning in the global example:
Reference to var 'animal' is not concurrency-safe because it involves shared mutable state
This compile-time detection of thread-safety issues is evolving, with many new errors promised in Swift 6 (which is why they’ve given us this new “Strict Concurrency Checking” setting so we can start reviewing our code with varying levels of checks).
Anyway, you can use an actor to offer thread-safe interaction with this value:
actor AnimalActor {
var animal = "Dog"
func setAnimal(newAnimal: String) {
animal = newAnimal
}
}
func testAsync() async {
let animalActor = AnimalActor()
Task {
try await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC)
await animalActor.setAnimal(newAnimal: "Cat")
print(await animalActor.animal)
}
print(await animalActor.animal)
}
Task {
await testAsync()
}
For more information, see WWDC 2021’s Protect mutable state with Swift actors and 2022’s Eliminate data races using Swift Concurrency.
Note, in the above, I have avoided using GCD API. The asyncAfter was the old, GCD, technique for deferring some work while not blocking the current thread. But the new Task.sleep (unlike the old Thread.sleep) achieves the same behavior within the concurrency system (and offers cancelation capabilities). Where possible, we should avoid GCD API in Swift concurrency codebases.
When you declare the variable inside an async function, it becomes part of the structured concurrency. Hypothetically your testAsync function can be run from any context. The change to animal, however, is done on the main thread, which introduces a data race.
In the second example, the variable is declared globally and operates on the main thread*. The compiler doesn’t strictly check for concurrency on global variables.
*: Actually, it is not guaranteed to run on the main thread. Like #Rob said, avoid using global variables.
I've created a Combine publisher chain that looks something like this:
let pub = getSomeAsyncData()
.mapError { ... }
.map { ... }
...
.flatMap { data in
let wsi = WebSocketInteraction(data, ...)
return wsi.subject
}
.share().eraseToAnyPublisher()
It's a flow of different possible network requests and data transformations. The calling code wants to subscribe to pub to find out when the whole asynchronous process has succeeded or failed.
I'm confused about the design of the flatMap step with the WebSocketInteraction. That's a helper class that I wrote. I don't think its internal details are important, but its purpose is to provide its subject property (a PassthroughSubject) as the next Publisher in the chain. Internally the WebSocketInteraction uses URLSessionWebSocketTask, talks to a server, and publishes to the subject. I like flatMap, but how do you keep this piece alive for the lifetime of the Publisher chain?
If I store it in the outer object (no problem), then I need to clean it up. I could do that when the subject completes, but if the caller cancels the entire publisher chain then I won't receive a completion event. Do I need to use Publisher.handleEvents and listen for cancellation as well? This seems a bit ugly. But maybe there is no other way...
.flatMap { data in
let wsi = WebSocketInteraction(data, ...)
self.currentWsi = wsi // store in containing object to keep it alive.
wsi.subject.sink(receiveCompletion: { self.currentWsi = nil })
wsi.subject.handleEvents(receiveCancel: {
wsi.closeWebSocket()
self.currentWsi = nil
})
Anyone have any good "design patterns" here?
One design I've considered is making my own Publisher. For example, instead of having WebSocketInteraction vend a PassthroughSubject, it could conform to Publisher. I may end up going this way, but making a custom Combine Publisher is more work, and the documentation steers people toward using a subject instead. To make a custom Publisher you have to implement some of things that the PassthroughSubject does for you, like respond to demand and cancellation, and keep state to ensure you complete at most once and don't send events after that.
[Edit: to clarify that WebSocketInteraction is my own class.]
It's not exactly clear what problems you are facing with keeping an inner object alive. The object should be alive so long as something has a strong reference to it.
It's either an external object that will start some async process, or an internal closure that keeps a strong reference to self via self.subject.send(...).
class WebSocketInteraction {
private let subject = PassthroughSubject<String, Error>()
private var isCancelled: Bool = false
init() {
// start some async work
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if !isCancelled { self.subject.send("Done") } // <-- ref
}
}
// return a publisher that can cancel the operation when
var pub: AnyPublisher<String, Error> {
subject
.handleEvents(receiveCancel: {
print("cancel handler")
self.isCancelled = true // <-- ref
})
.eraseToAnyPublisher()
}
}
You should be able to use it as you wanted with flatMap, since the pub property returned publisher, and the inner closure hold a reference to self
let pub = getSomeAsyncData()
...
.flatMap { data in
let wsi = WebSocketInteraction(data, ...)
return wsi.pub
}
I have an observable inside a function.
The function happens in a certain queue, queueA, and the observable is subscribed to with observeOn(schedulerB). In onNext, I'm changing a class variable.
In another function, I'm changing the same class variable, from a different queue.
Here is some code to demonstrate my situation:
class SomeClass {
var commonResource: [String: String] = [:]
var queueA = DispatchQueue(label: "A")
var queueB = DispatchQueue(label: "B")
var schedulerB = ConcurrentDispatchQueueScheduler(queue: QueueB)
func writeToResourceInOnNext() {
let obs: PublishSubject<String> = OtherClass.GetObservable()
obs.observeOn(schedulerB)
.subscribe(onNext: { [weak self] res in
// this happens on queue B
self.commonResource["key"] = res
}
}
func writeToResource() {
// this happens on queue A
commonResource["key"] = "otherValue"
}
}
My question is, is it likely to have concurrency issues, if commonResource is modified in both places at the same time?
What is the common practice for writing/reading from class/global variables inside onNext in an observable with observeOn?
Thanks all!
Since your SomeClass has no control over when these functions will be called or on what threads the answer is yes, you are setup to have concurrency issues in this code due to its passive nature.
The obvious solution here is to dispatch to queue B inside writeToResource() in order to avoid the race condition.
Another option would be to use an NSLock (or NSRecursiveLock) and lock it before you write to the resource and unlock it after.
The best practice is: when you have a side effect happening inside a subscribe function's closure (in this case writing to commonResource that the closure is the only place where the side effect occurs. This would mean doing away with the passive writeToResource() function and instead passing in an Observable that was generated by whatever code currently is calling the function.
Swift 5, the "Exclusive Access to Memory" enforcement is now on by default for release builds as mentioned in this Swift.org blog post:
Swift 5 Exclusivity Enforcement
I understand the reasoning behind this feature, but with the new Combine framework I feel as if some very normal design patterns are now going to break and I'm curious how best to work around them.
With Combine it's natural for parts of your code to react to changes in a model such that they might need to read from the very property that the model has just changed. But they can no longer do that because it will trigger a memory exception as you attempt to read a value that is currently being set.
Consider the following example:
struct PasswordProposal {
let passwordPublisher = CurrentValueSubject<String, Never>("1234")
let confirmPasswordPublisher = CurrentValueSubject<String, Never>("1234")
var password:String {
get { passwordPublisher.value }
set { passwordPublisher.value = newValue }
}
var confirmPassword:String {
get { confirmPasswordPublisher.value }
set { confirmPasswordPublisher.value = newValue }
}
var isPasswordValid:Bool {
password == confirmPassword && !password.isEmpty
}
}
class Coordinator {
var proposal:PasswordProposal
var subscription:Cancellable?
init() {
self.proposal = PasswordProposal()
self.subscription = self.proposal.passwordPublisher.sink { [weak self] _ in
print(self?.proposal.isPasswordValid ?? "")
}
}
// Simulate changing the password to trigger the publisher.
func changePassword() {
proposal.password = "7890"
}
}
// --------------------------------
var vc = Coordinator()
vc.changePassword()
As soon as changePassword() is called, the mutual exclusivity enforcement will throw an exception because the property password will attempt to be read from while it's currently being written to.
Note that if you change this example to use a separate backing storage property instead of the CurrentValueSubject it causes the same exception.
However, if you change PasswordProposal from being a struct to a class, then the exception is no longer thrown.
When I consider how I might use Combine in an existing codebase, as well as in SwiftUI, I see this type of pattern coming up in a lot of places. In the old delegate model, it's quite common for a delegate to query the sending object from within a delegate callback. In Swift 5, I now have to be very careful that none of those callbacks potentially read from the property that initiated the notification.
Have others come across this and, if so, how have you addressed it? Apple has routinely suggested that we should be using structs where it makes sense but perhaps an object that has published properties is one of those areas where it doesn't?
The password property is not the problem. It's actually the proposal property. If you add a didSet property observer to proposal, you'll see it's getting reset when you set password, then you access self?.proposal from within your sink while it's being mutated.
I doubt this is the behavior that you want, so it seems to me like the correct solution is to make PasswordProposal a class.
I have instances of type Set<CostumObject> that I want to archive using a NSKeyedArchiver.
Assume customObject1: CostumObject and customObject2: CostumObject are instantiated somewhere.
If I use the following statements:
let setOfCostomObjects: Set<CostumObject> = [customObject1, customObject2]
let data = NSKeyedArchiver.archivedData(withRootObject: setOfCostomObjects)
NSKeyedArchiver archives sequentially both custom objects where their properties are archived recursively.
This is not thread-safe, since another thread can mutate both custom objects and their properties during archiving.
I think I can thread-safe archive each property of the custom object, so that concurrent gets are allowed but only a single set, by using a concurrent queue with a barrier for set like:
private let concurrentPropertyAccessQueue = DispatchQueue(label: "concurrentPropertyAccessQueue", attributes: .concurrent)
…
private var safeProperty = CostumProperty.init()
public private(set) var property: CostumProperty {
get {
var result = CostumProperty.init()
concurrentPropertyAccessQueue.sync { result = safeProperty } // sync, because result is returned
return result
} // get
set { concurrentPropertyAccessQueue.async(flags: .barrier) { safeProperty = newValue } // executes locked after all gets
} // set
}
…
public func threadSafeArchiveOfProperty() -> Data {
var data = Data.init()
concurrentPropertyAccessQueue.sync { // sync, because result is returned
data = NSKeyedArchiver.archivedData(withRootObject: self.safeProperty)
}
return data
}
I think I can also thread-safe archive the whole custom object in a similar way:
private let concurrentObjectAccessQueue = DispatchQueue(label: "concurrentObjectAccessQueue", attributes: .concurrent)
…
public func encode(with aCoder: NSCoder) {
concurrentObjectAccessQueue.async(execute: {
aCoder.encode(self.property forKey: "property")
…
})
}
The problem still is, how to thread-safe archive the set of custom objects.
This would require that write accesses to the elements of the set are locked out during archiving.
One way to do so is probably to define a global concurrent queue:
public let globalConcurrentAccessQueue = DispatchQueue(label: "globalConcurrentAccessQueue", attributes: .concurrent)
To lock the set and all its elements during archiving, one could probably write an extension to the Set type that defines a func threadSafeArchiveOfSet() as above.
This function would then override the Set’s encode(with aCoder: NSCoder), so that the globalConcurrentAccessQueue is locked.
Is this the right way to go?
I think this is a standard problem that should have a standard solution.
Often, property-level synchronization is simply inadequate. It provides thread-safe access to the individual properties, but it does not ensure thread-safe access to the broader object where there might be interdependencies between different properties. The prototypical example is a Person object with first and last name properties. Synchronization changes to the first and last name separately can still end up with the object being captured in an internally inconsistent state. You often need to synchronize the object at a higher level, and if you do that, it renders the property-level synchronization redundent.
A few unrelated observations:
The encode method must perform its task synchronously, not asychronously. The caller assumes the encoding is complete by the time it returns. I can guess why you may have made it asynchronous (e.g. it isn't explicitly returning anything, after all), but the question isn't whether anything is returned, but rather more broadly whether there are any side-effects outside of the the synchronized object. In this case there are (you're updating the NSCoder object), so you must use sync in encode.
A couple of times you employ a pattern of initializing a variable, calling sync to modify that local variable, and then returning that value. E.g.
func threadSafeArchiveOfProperty() -> Data {
var data = Data.init()
concurrentPropertyAccessQueue.sync { // sync, because result is returned
data = NSKeyedArchiver.archivedData(withRootObject: self.safeProperty)
}
return data
}
But sync offers a nice way to simplify this, namely if the closure returns a value, sync will return it, too. And if the closure has only one line, you don't even need explicit return in the closure:
func threadSafeArchiveOfProperty() -> Data {
return concurrentPropertyAccessQueue.sync { // sync, because result is returned
NSKeyedArchiver.archivedData(withRootObject: self.safeProperty)
}
}
Basem Emara described here a solution for thread-safe arrays that can be applied also to sets:
He declared a SynchronizedArray to mimic a regular array. In it contains a private concurrent queue and array, and exposed several of the array’s properties and methods.
Immutable accesses are done synchronously and concurrently, while mutable accesses are done asynchronously with a barrier, i.e. after all other blocks in the queue terminated.