Swift: Thread safe Singleton, why do we use sync for read? - swift

While making a thread-safe Singleton, it is advised to use a sync for read and an async with a barrier for write operation.
My question is why do we use a sync for read? What might happen if we perform a read with async operation?
Here is an example of what is recommended:
func getUser(id: String) throws -> User {
var user: User!
try concurrentQueue.sync {
user = try storage.getUser(id)
}
return user
}
func setUser(_ user: User, completion: (Result<()>) -> Void) {
try concurrentQueue.async(flags: .barrier) {
do {
try storage.setUser(user)
completion(.value(())
} catch {
completion(.error(error))
}
}
}

The concept of using concurrent queue with “read concurrently with sync; write with barrier with async” is a very common synchronization pattern called “reader-writer”. The idea is that the concurrent queue is just for synchronizing writes with a barrier, but that reads will take place concurrently with respect to other reads.
So, here’s a simple, real-world example of using reader-writer for synchronized access to some private state property:
enum State {
case notStarted
case running
case complete
}
class ComplexProcessor {
private var readerWriterQueue = DispatchQueue(label: "...", attributes: .concurrent)
// private backing stored property
private var _state: State = .notStarted
// exposed computed property synchronizes access using reader-writer pattern
var state: State {
get { readerWriterQueue.sync { _state } }
set { readerWriterQueue.async { self._state = newValue } }
}
func start() {
state = .running
DispatchQueue.global().async {
// do something complicated here
self.state = .complete
}
}
}
Consider:
let processor = ComplexProcessor()
processor.start()
And then, later:
if processor.state == .complete {
...
}
The state computed property is using the reader-writer pattern to offer thread-safe access to the underlying stored property. It synchronizes access to some memory location, and we are confident that it will be responsive. In this case, we don’t need confusing #escaping closures: The sync reads result in very simple code that is easy to reason about.
That having been said, in your example, you’re not just synchronizing interaction with some property, but synchronizing the interaction with storage. If that’s local storage that is guaranteed to be responsive, then the reader-writer pattern is probably fine.
But if storage methods could take anything more than a few milliseconds to run, you wouldn’t want to use the reader-writer pattern. The fact that getUser can throw errors makes me wonder if storage is already doing complicated processing. And even if it is just reading quickly from some local store, what if it was later refactored to interact with some remote store, subject to unknown network latency/issues? Bottom line, it is questionable to have the getUser method making assumptions about implementation details of storage, assuming that the value will always be returned quickly.
In that case, you would refactor getUser method to use #escaping completion handler closure, as suggested by Jeffery Thomas. We never want to have a synchronous method that might take more than a few milliseconds, because we never want to block the calling thread (especially if it’s the main thread).
By the way, if you stay with reader-writer pattern, you can simplify your getUser, because sync returns whatever value its closure returns:
func getUser(id: String) throws -> User {
return try concurrentQueue.sync {
try storage.getUser(id)
}
}
And you can’t use try in conjunction with async (only within your do-catch block). So it’s just:
func setUser(_ user: User, completion: (Result<()>) -> Void) {
concurrentQueue.async(flags: .barrier) {
do {
try storage.setUser(user)
completion(.value(())
} catch {
completion(.error(error))
}
}
}

It's all in what you want. By changing get user to async, then you need to use a callback to wait for the value.
func getUser(id: String, completion: #escaping (Result<User>) -> Void) -> Void {
concurrentQueue.async {
do {
let user = try storage.getUser(id)
completion(.value(user))
} catch {
completion(.error(error))
}
}
}
func setUser(_ user: User, completion: #escaping (Result<()>) -> Void) {
concurrentQueue.async(flags: .barrier) {
do {
try storage.setUser(user)
completion(.value(()))
} catch {
completion(.error(error))
}
}
}
That changes the API of get user, so now when calling get user, a callback will need to be used.
Instead of somethings like this
do {
let user = try manager.getUser(id: "test")
updateUI(user: user)
} catch {
handleError(error)
}
you will need something like this
manager.getUser(id: "test") { [weak self] result in
switch result {
case .value(let user): self?.updateUI(user: user)
case .error(let error): self?.handleError(error)
}
}
Assuming you have somethings like a view controller with a property named manager and methods updateUI() and handleError()

Related

How to cancel an `async` function with cancellable type returned from `async` operation initiation

I need to support cancellation of a function that returns an object that can be cancelled after initiation. In my case, the requester class is in a 3rd party library that I can't modify.
actor MyActor {
...
func doSomething() async throws -> ResultData {
var requestHandle: Handle?
return try await withTaskCancellationHandler {
requestHandle?.cancel() // COMPILE ERROR: "Reference to captured var 'requestHandle' in concurrently-executing code"
} operation: {
return try await withCheckedThrowingContinuation{ continuation in
requestHandle = requester.start() { result, error in
if let error = error
continuation.resume(throwing: error)
} else {
let myResultData = ResultData(result)
continuation.resume(returning: myResultData)
}
}
}
}
}
...
}
I have reviewed other SO questions and this thread: https://forums.swift.org/t/how-to-use-withtaskcancellationhandler-properly/54341/4
There are cases that are very similar, but not quite the same. This code won't compile because of this error:
"Reference to captured var 'requestHandle' in concurrently-executing code"
I assume the compiler is trying to protect me from using the requestHandle before it's initialized. But I'm not sure how else to work around this problem. The other examples shown in the Swift Forum discussion thread all seem to have a pattern where the requester object can be initialized before calling its start function.
I also tried to save the requestHandle as a class variable, but I got a different compile error at the same location:
Actor-isolated property 'profileHandle' can not be referenced from a
Sendable closure
You said:
I assume the compiler is trying to protect me from using the requestHandle before it’s initialized.
Or, more accurately, it is simply protecting you against a race. You need to synchronize your interaction with your “requester” and that Handle.
But I’m not sure how else to work around this problem. The other examples shown in the Swift Forum discussion thread all seem to have a pattern where the requester object can be initialized before calling its start function.
Yes, that is precisely what you should do. Unfortunately, you haven’t shared where your requester is being initialized or how it was implemented, so it is hard for us to comment on your particular situation.
But the fundamental issue is that you need to synchronize your start and cancel. So if your requester doesn’t already do that, you should wrap it in an object that provides that thread-safe interaction. The standard way to do that in Swift concurrency is with an actor.
For example, let us imagine that you are wrapping a network request. To synchronize your access with this, you can create an actor:
actor ResponseDataRequest {
private var handle: Handle?
func start(completion: #Sendable #escaping (Data?, Error?) -> Void) {
// start it and save handle for cancelation, e.g.,
handle = requestor.start(...)
}
func cancel() {
handle?.cancel()
}
}
That wraps the starting and canceling of a network request in an actor. Then you can do things like:
func doSomething() async throws -> ResultData {
let responseDataRequest = ResponseDataRequest()
return try await withTaskCancellationHandler {
Task { await responseDataRequest.cancel() }
} operation: {
return try await withCheckedThrowingContinuation { continuation in
Task {
await responseDataRequest.start { result, error in
if let error = error {
continuation.resume(throwing: error)
} else {
let resultData = ResultData(result)
continuation.resume(returning: resultData)
}
}
}
}
}
}
You obviously can shift to unsafe continuations when you have verified that everything is working with your checked continuations.
After reviewing the Swift discussion thread again, I see you can do this:
...
var requestHandle: Handle?
let onCancel = { profileHandle?.cancel() }
return try await withTaskCancellationHandler {
onCancel()
}
...

Not getting all expected events when subscribing to sequence with TestScheduler

I'm trying to write an integration test for a Reactor in an app built with ReactorKit and Realm/RxRealm.
I'm having trouble using TestScheduler to simulate user actions and test the expected emitted states.
In a nutshell, my problem is this: I'm binding an action that will make my Reactor save an item to Realm, my Reactor also observes changes to this object in Realm, and I expect my Reactor to emit the new state of this item observed from Realm.
What I'm seeing is that my test does not get the emission of the newly saved object in time to assert its value, it's emitted after my test assertion runs.
There is a fair amount of code involved, but attempting to whittle it down into a self-contained example of what it all roughly looks like below:
struct MyObject {
var counter: Int = 0
}
class MyReactor: Reactor {
enum Action {
case load
case mutateState
}
enum Mutation {
case setObject(MyObject)
}
struct State {
var object: MyObject?
}
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .load:
return service.monitorObject().map(Mutation.setObject)
case .mutateState:
guard var myObject = currentState.object else { return .empty() }
myObject.counter += 1
return service.save(myObject).andThen(.empty())
}
}
func reduce(state: State, mutation: Mutation) -> Observable<State> {
var newState = state
switch mutation {
case let .setObject(object):
// Should be called twice in tests, once on load, once after mutateState action
newState.object = object
}
}
}
struct Service {
// There is always at least one default instance of `MyObject` in Realm.
func monitorObject() -> Observable<MyObject> {
return Observable
.collection(from: realm.objects(MyObject.self))
.map { $0.first! }
}
func save(_ object: MyObject) -> Completable {
return Completable.create { emitter in
try! realm.write {
realm.add(object, update: .modified)
}
emitter(.completed)
return Disposables.create()
}
}
}
class MyTest: QuickSpec {
var scheduler: TestScheduler!
var sut: MyReactor!
var disposeBag: DisposeBag!
var service: Service!
var config: Realm.Configuration!
override func spec() {
beforeEach {
config = Realm.Configuration(inMemoryIdentifier: UUID().uuidString)
scheduler = TestScheduler(initialClock: 0)
disposeBag = DisposeBag()
sut = MyReactor()
service = Service(realmConfig: config)
}
describe("when my reactor gets a mutateState action") {
it("should mutate state") {
scheduler.createHotObservable([
.next(1, Action.load),
.next(2, Action.mutateState),
])
.bind(to: sut.action)
.disposed(by: disposeBag)
let response = scheduler.start(created: 0, subscribed: 0, disposed: 1000) {
sut.state.map(\.object)
}
// Counter always equals 0
XCTAssertTrue(response.events.last!.value.element!!.counter == 1)
}
}
}
}
What I'm expecting to happen is my Reactor's state is set for a 2nd time, before the XCTAssertTrue is hit. What is actually happening is the assert is hit with the initially loaded state, and then, my reactor's state is set again.
I thought my problem might be related to schedulers. Something I tried was injecting the test scheduler into my Service and doing observeOn(testScheduler) on my monitorObject function. But I'm still observing the assert get hit before the reactor's state is set for the 2nd time. I'm also not sure if a nuance of RxRealm/Realm change set notifications is the cause - not sure how to verify whether that might be the case.
Hopefully the problem and question is clear. Thanks in advance for any help.
I decided attempting to write an integration test was more trouble than it was worth and probably not going to result in very useful tests anyway.
So you are trying to test to see if Realm works. I don't use Realm, but based on your description, it probably updates the object on an internal thread and then you get the emission on a subsequent cycle.
You can test it by using an XCTestExpectation. Here is documentation from Apple: https://developer.apple.com/documentation/xctest/asynchronous_tests_and_expectations/testing_asynchronous_operations_with_expectations
Note however, that if something goes wrong in Realm and this test fails, there isn't anything you can do about it.

Realm Transaction without notifying tokens

I have Realm notifications on a background thread created with the following code (taken from Realm's website)
class BackgroundWorker: NSObject {
private let name: String
private var thread: Thread!
private var block: (()->Void)!
init(name: String) {
self.name = name
}
#objc internal func runBlock() {
block()
}
internal func start(_ block: #escaping () -> Void) {
self.block = block
if thread == nil {
createThread()
}
perform(
#selector(runBlock),
on: thread,
with: nil,
waitUntilDone: false,
modes: [RunLoop.Mode.default.rawValue]
)
}
private func createThread() {
thread = Thread { [weak self] in
while (self != nil && !self!.thread.isCancelled) {
RunLoop.current.run(
mode: RunLoop.Mode.default,
before: Date.distantFuture)
}
Thread.exit()
}
thread.name = name
thread.start()
}
func stop() {
thread.cancel()
}
}
And using the background worker like this
struct RealmBackGroundWorker {
static var tokens: [NotificationToken] = []
static let backgroundWorker = BackGroundWorker(name: "RealmWorker")
static func start() {
backgroundWorker.start {
self.tokens = ...
}
}
}
The background notifications work great. But I often need to save data to realm without notifying these transactions. From what I have found, it does not look like there is a way write data without notifying all tokens. You always have to specify the tokens you want to ignore.
How can I write data to the Realm without notifying these background tokens?
Let me preface this answer with a couple of things. The Realm website the OP got their code from was here Realm Notifications on Background Threads with Swift and part of the point of that code was to not only spin up a runloop on a background thread to handle Realm functions but to also handle notifications on that same thread.
That code is pretty old - 4+ years and is somewhat outdated. In essence, there are possibly better options. From Apple:
... newer technologies such as Grand Central Dispatch (GCD) provide a
more modern and efficient infrastructure for implementing concurrency
But to address the question, if an observer is added to a Realm results on thread A, then all of the notifications will also occur on thread A. e.g. the token returned from the observe function is tied to that thread.
It appears the OP wants to write data without receiving notifications
I do not want to sync local changes to the server, so I would like to
call .write(withouNotifying: RealmWorkBlock.tokens)
and
I want a way to write data to the realm database without notifying
these notifications.
Noting that those notifications will occur on the same thread as the runloop. Here's the code that we need to look at
static func start() {
backgroundWorker.start {
self.tokens = ...
}
and in particular this line
self.tokens = ...
because the ... is the important part. That ... leads to this line (from the docs)
self?.token = files.observe { changes in
which is where the observer is added that generates the notifications. If no notifications are needed then that code, starting with self?.token can be completely removed as that's is sole purpose - to generate notifications.
One thought is to add a different init to the background worker class to have a background worker with no notifications:
static func start() {
backgroundWorker.startWithoutNotifications()
}
Another thought is to take a more modern approach and leverage DispatchQueue with an autorelease pool which eliminates the need for these classes completely, will run in the background freeing up the UI ad does not involve tokens or notifications.
DispatchQueue(label: "background").async {
autoreleasepool {
let realm = try! Realm()
let files = realm.objects(File.self).filter("localUrl = ''")
}
}

How to utilize PromiseKit to ensure a queried object has been retrieved before proceeding?

I am programming an app which utilizes a parse-server (hosted by heroku) database. I have several functions which pull information from the DB, but they are all inherently asynchronous (because of the way parse's .findObjectinBackground works.) The issue with this as that the later DB queries require information from previous queries. Since the information being pulled is asynchronous, I decided to implement PromiseKit to ensure that the object has been found from findObjectinBackground from the first query, before running the second query.
The general form of the queries is as follows:
let query = PFQuery(classname: "Hello")
query?.findObjectsInBackground(block: { (objects, error) in
if let objectss = objects{
for object in objectss{ //object needs to be pulled
arrayOfInterest.append(object)
//array must be appended before moving on to next query
}
}
})
I just do not know how exactly to do this. This is the way I would like to implement it:
import PromiseKit
override func viewDidLoad(){
when(/*query object is retrieved/array is appended*/).then{
//perform the next query
}
}
I simply don't know exactly what to put in the when() and the .then{}. I tried making the queries into their own individual functions and calling them inside those two (when and then) functions, but I basically get told that I cannot because they return void. Also, I cannot simply ensure the first query is run in the when() as the query.findObjectinBackground(in the query) being asynchronous is the issue. The object specifically needs to be pulled, not just the query run, before the next one can fire.
Do you want create your promise?
You need write a function that return a Promise<Any>. In your case, need to encapsulate the entire code inside of Promise { fulfill, reject in HERE}. For example:
func foo(className: String) -> Promise<[TypeOfArrayOfInterest]> {
return Promise { fulfill, reject in
let query = PFQuery(classname: className)
query?.findObjectsInBackground(block: { (objects, error) in
if let error = error {
reject(error) // call reject when some error happened
return
}
if let objectss = objects {
for object in objectss{
arrayOfInterest.append(object)
}
fulfill(arrayOfInterest) // call fulfill with some value
}
})
}
}
Then, you call this function in firstly:
firstly {
foo(className: "Hello")
}.then { myArrayOfInterest -> Void in
// do thing with myArrayOfInterest
}.catch { error in
// some error happened, and the reject was called!
}
Also, I wrote a post in my blog about, among other things, PromiseKit and architecture. It may be helpful: http://macalogs.com.br/ios/rails/ifce/2017/01/01/experiencias-eventbee.html
Edit
More complete example:
func foo() -> Promise<Int> {
...
}
func bar(someText: String) -> Promise<String> {
...
}
func baz() -> Promise<Void> {
...
}
func runPromises() {
firstly {
foo()
}.then { value -> Promise<Any> in
if value == 0 {
return bar(someText: "no")
} else {
return bar(someText: "yes")
}
}.then { _ /* I don't want a String! */ -> Promise<Void> in
baz()
}.catch { error in
// some error happened, and the reject was called!
}
}
Or if you don't want a catch:
_ = firstly {
foo()
}.then { _ in
// do some thing
}
Swift have a greate type inference, but, when use PromiseKit, I recommend always write a type in then closure, to avoid erros.

Can Swift return value from an async Void-returning block?

I want to create a function to check if user_id is already in my database.
class func checkIfUserExsits(uid:String) -> Bool {
userRef.childByAppendingPath(uid).observeSingleEventOfType(.Value, withBlock: { (snapShot: FDataSnapshot!) -> Void in
if snapShot.value is NSNull {
return false
} else {
return true
}
})
}
However, observeSingleEventOfType is a API provided by 3rd party Firebase. It is defined to return Void.
(void)observeSingleEventOfType:(FEventType)eventType withBlock:(void ( ^ ) ( FDataSnapshot *snapshot ))block
Error: Type 'Void' does not conform to protocol 'BooleanLiteralConvertible'
Appreciate any kind of helps.
UPDATE
I am trying a different way:
class func checkIfExist(uid: String) -> Bool {
var answer:Bool = false
var text:String = "not yet completed"
let queue = dispatch_group_create()
dispatch_group_enter(queue)
userRef.childByAppendingPath(uid).observeSingleEventOfType(.Value, withBlock: { (snapShot: FDataSnapshot!) -> Void in
if snapShot.value is NSNull {
text = "This is a new user"
answer = false
dispatch_group_leave(queue)
} else {
text = "Found the user in Firebase"
answer = true
dispatch_group_leave(queue)
}
})
dispatch_group_wait(queue, DISPATCH_TIME_FOREVER)
println(text)
return answer
}
Somehow it just freeze there. I know this approach could be off-topic now. But please help.
You should employ asynchronous (ie, escaping) completion handler yourself:
class func checkIfUserExists(uid: String, completion: #escaping (Bool) -> Void) {
userRef.childByAppendingPath(uid).observeSingleEventOfType(.Value) { snapShot in
if snapShot.value is NSNull {
completion(false)
} else {
completion(true)
}
}
}
You can then call this like so:
MyClass.checkIfUserExists(uid) { success in
// use success here
}
// but not here
In your revised question, you demonstrate the use of dispatch groups to make this asynchronous method behave synchronously. (Semaphores are also often used to the same ends.)
Two issues:
This will deadlock if they dispatch their completion handler back to the main queue (and in many cases, libraries will do this to simplify life for us), because you're coincidentally blocking the very same thread they're trying to use. I don't know if that's what they've done here, but is likely.
If you want to confirm this, temporarily remove dispatch group and then examine NSThread.isMainThread and see if it's running in main thread or not.
You never should block the main thread, anyway. They provided an asynchronous interface for good reason, so you should use asynchronous patterns when calling it. Don't fight the asynchronous patterns, but rather embrace them.