How to thread-safe archive a set of custom objects? - swift

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.

Related

Use NWPathMonitor with Swift Modern Concurrency (AsyncStream) vs GCD (DispatchQueue)

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.

How do I wait for a download to complete before continuing?

I have this block of code. It fetches data from the API and adds it to a locationDetails array, which is part of a singleton.
private func DownloadLocationDetails(placeID: String) {
let request = AF.request(GoogleAPI.shared.getLocationDetailsLink(placeID: placeID))
request.responseJSON { (data) in
guard let detail = try? JSONDecoder().decode(LocationDetailsBase.self, from: data.data!),
let result = detail.result else {
print("Something went wrong fetching nearby locations.")
return
}
DownloadManager.shared.locationDetails.append(result)
}
}
This block of code is the block in question. I'm creating a caching system of sorts that only downloads new information and retains any old information. This is being done to save calls to the API and for performance gains. The line DownloadLocationDetails(placeID: placeID) is a problem for me because if I execute this line of code it will continue to loop over and over again using unnecessary API calls while waiting for the download to complete. How do I effectively manage this?
func GetLocationDetail(placeID: String) -> LocationDetail {
for location in locationDetails {
if location.place_id == placeID { return location }
}
DownloadLocationDetails(placeID: placeID)
return GetLocationDetail(placeID: placeID)
}
I expect this GetLocationDetail(....) to be called whenever a user interacts with an interface object, so how do I also ensure that the view that calls this is properly notified that the download is complete?
I attempted using a closure but I can't get it to return the way I'm wanting it to. I have a property on the singleton that I want to set this value so that it can be called globally. I am also considering using GCD but I'm not sure of the structure for that.
Generally the pattern for something like this is to store the request object you created in DownloadLocationDetails so you can check to see if one is active before making another call. If you only want to support one at a time, then it's as simple as keeping the bare reference to the request object, but you could make a dictionary of request objects keyed off the placeID (and you probably want to think about maximum request count, and queue up additional requests).
Then the trick is to get notified when the given request object completes. There are a couple ways you could do this, such as keeping a list of callbacks to invoke when it completes, but the easiest would probably be just to refactor the code a bit so that you always update your UI when the request completes, so something like:
private func DownloadLocationDetails(placeID: String) {
let request = AF.request(GoogleAPI.shared.getLocationDetailsLink(placeID: placeID))
request.responseJSON { (data) in
guard let detail = try? JSONDecoder().decode(LocationDetailsBase.self, from: data.data!),
let result = detail.result else {
print("Something went wrong fetching nearby locations.")
return
}
DownloadManager.shared.locationDetails.append(result)
// Notify the UI to refresh for placeID
}
}

Concurrency issues with observable.observeOn() and common resources

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.

How to set the value of lazy computed property via a closure in Swift?

So I've been stuck on this problem for a while, and can't find questions addressing my particular problem online.
I am trying to set the value in description, which is defined as a lazy computed property and utilizes a self-executing closure.
To get the book's description, I make an API call, passing in another handler to the API completion handler so that I can set the book's description inside the lazy computed property.
I know my below code is wrong, since I get the error:
Cannot convert value of type '()' to specified type 'String'
class Book : NSObject {
func getInfo(for name: String, handler: #escaping (_ string: String) -> String) {
let task = URLSession.shared.dataTask(with: "foo_book.com" + name) { (data, response, error) in
guard let data = data else {return}
descriptionStr = String(data: data, encoding: .utf8) ?? "No description found"
handler(descriptionStr)
}
}
lazy var description: String = {
getInfo(for: self.name) { str in
return str
}
}()
}
How can I set the value of description?
I've tried two methods. Using a while loop to wait for a boolean: inelegant and defeats the purpose of async. Using a temp variable inside description - doesn't work because getInfo returns before the API call can finish.
In case you wonder my use case: I want to display books as individual views in a table view, but I don't want to make api calls for each book when I open the tableview. Thus, I want to lazily make the API call. Since the descriptions should be invariant, I'm choosing to make it a lazy computed property since it will only be computed once.
Edit: For those who are wondering, my solution was as the comments mentioned below. My approach wasn't correct - instead of trying to asynchronously set a property, I made a method and fetched the description in the view controller.
Already the explanation in comments are enough for what's going wrong, I will just add on the solution to your use case.
I want to display books as individual views in a table view, but I
don't want to make api calls for each book when I open the tableview.
Thus, I want to lazily make the API call.
First of all, does making lazy here make sense. Whenever in future you will call description, you are keeping a reference for URLSession and you will do it for all the books. Looks like you will easily create a memory leak.
Second, task.resume() is required in getInfo method.
Third, your model(Book) should not make the request. Why? think, I have given one reason above. Async does mean parallel, all these network calls are in the queue, If you have many models too many networks calls in the event loop.
You can shift network call responsibility to service may be BookService and then have a method like this BookService.getInfo(_ by: name). You Book model should be a dumb class.
class Book {
let description: String
init(desc: String) {
self.description = desc
}
}
Now your controller/Interactor would take care of calling the service to get info. Do the lazy call here.
class BookTableViewController: ViewController {
init(bookService: BookService, book: [String]) {
}
# you can call when you want to show this book
func loadBook(_ name: String) -> Book {
BookService.getInfo(name).map { Book(desc: str) }
}
func tableView(UITableView, didSelectRowAt: IndexPath) {
let bookName = ....
# This is lazy loading
let book = loadBook(bookName)
showThisBook()
}
}
Here, you can do the lazy call for loadBook. Hope this helps.

How to call every struct method inside write transaction

I created struct Repository for manipulating with objects of Realm database (changing some properties, adding new objects, deleting, etc.). When I want to write to the database, I have to do it inside do-try-catch block, so I created a method with completion which I call every time I need to write something to the database
private func action(_ completion: () -> Void) {
do {
try realm.write {
completion()
}
} catch {
print(error)
}
}
then I call methods for manipulating with objects like this:
func createObject(_ object: MyObject) {
action {
realm.add(object)
}
}
func deleteObject(_ object: MyObject) {
action {
realm.delete(object)
}
}
func setTitleForObject(_ object: MyObject, title: String) {
action {
object.title = title
}
}
...
My question is, is there any way how I can call every method inside this Repository struct inside write transaction in do-try-catch block by default instead of calling it inside completion of action? (or is some better way how to write to the Realm database without do-try-catch block?)
Short answer is no, there is no way to write data to realm without write transaction and without try-catch.
realm.write() is a convenient wrapper of transaction building with beginWrite() and commitWrite() calls.
These two functions build a transaction and commitWrite() is throwable, so you need to wrap to try-catch, anyway.
See https://realm.io/docs/swift/latest#writes
Example of using beginWrite()+commitWrite() https://realm.io/docs/swift/latest#interface-driven-writes
There are a lot of failures could happen during write transactions. So, simply, it is not safe to not to handle it somehow.
Also grouping write transactions by "action" is not a good idea if you going to process big amounts of objects because write transactions are costly. You'd rather group these changes to a single transaction instead of having a lot of small transactions.