Realm notifications registration while in write transaction - swift

I understand that you can not register a Realm .observe block on an object or collection if the Realm is in a write transaction.
This is easier to manage if everything is happening on the main thread however I run into this exception often because I prefer to hand my JSON parsing off to a background thread. This works great because I don't have to bog down the main thread and with Realm's beautiful notification system I can get notified of all modifications if I have already registered to listen for those changes.
Right now, if I am about to add an observation block I check to make sure my Realm is not in a write transaction like this:
guard let realm = try? Realm(), !realm.isInWriteTransaction else {
return
}
self.myToken = myRealmObject.observe({ [weak self] (change) in
//Do what ever
}
This successfully guards against this exception. However I never get a chance to re - register this token unless I get a little creative.
Does the Realm team have any code examples/ suggestions on a better pattern to avoid this exception? Any tricks I'm missing to successfully register the token?

In addition to the standard function, I do use an extension for Results to avoid this in general. This issue popped up, when our data load grew bigger and bigger.
While we do now rewrite our observe functions logic, this extension is an interims solution to avoid the crashes at a first place.
Idea is simple: when currently in a write transaction, try it again.
import Foundation
import RealmSwift
extension Results {
public func safeObserve(on queue: DispatchQueue? = nil,
_ block: #escaping (RealmSwift.RealmCollectionChange<RealmSwift.Results<Element>>) -> Void)
-> RealmSwift.NotificationToken {
// If in Write transaction, call it again
if self.realm?.isInWriteTransaction ?? false {
DispatchQueue.global().sync {
Thread.sleep(forTimeInterval: 0.1) // Better to have some delay than a crash, hm?
}
return safeObserve(on: queue, block)
}
// Aight, we can proceed to call Realms Observe function
else {
return self.observe(on: queue, block)
}
}
}
Then call it like
realmResult.safeObserve({ [weak self] (_: RealmCollectionChange<Results<AbaPOI>>) in
// Do anything
})

Related

Combine: How to clean up resources while an AnyCancellable is being cancelled?

Overview:
I have a async task to fetch from the database
I have created a Future for the async task (fetching from the database).
Question:
How can execute custom code when the Future is cancelled?
Purpose:
I would like the database connection to be closed when the subscription is cancelled.
For example, I would like to use Combine to rewrite this helper method:
// Similar to https://developer.apple.com/documentation/coredata/nspersistentcontainer/1640564-performbackgroundtask
func withDatabaseFTSContext(block: #escaping (FMDatabase?) -> Void) {
queue.async {
guard let database = self.database else {
block(nil)
return
}
database.open()
let simpleTokenizer = FMSimpleTokenizer(locale: nil)
FMDatabase.registerTokenizer(simpleTokenizer, withKey: "simple")
database.installTokenizerModule()
block(database)
database.close()
}
}
Could I leverage Combine to rewrite this method to return FMDatabase as a parameter of a publisher?
I was attempting to use Combine but it does not work. The database will be closed before cancel()
private func withDatabaseFTSContext() -> AnyPublisher<FMDatabase?, Never> {
return Future<FMDatabase?, Never> { promise in
self.queue.async {
guard let database = self.database else {
promise(.success(nil))
return
}
database.open()
let simpleTokenizer = FMSimpleTokenizer(locale: nil)
FMDatabase.registerTokenizer(simpleTokenizer, withKey: "simple")
database.installTokenizerModule()
promise(.success(database))
database.close() // When to close this database? Currently it will be closed before `cancel()`
}
}.eraseToAnyPublisher()
}
Short answer: there isn't a callback that triggers through to the underlying Future that you can use to clean things up on a subscriber cancel. In the Combine design, these functions are very intentionally separated and don't have reference links back to their publishers.
(In addition, Future is a tricky figure in the Combine world because the closure is invoked immediately upon creation time, rather than when you have a subscription (if you want that, wrap in the Future publisher in a Deferred publisher)).
All that being said, what you likely want to do to solve your underlying problem is reframe how you're treating this to separate the concerns of managing the FMDB instance and publishing data. One pattern that's been reasonably useful in this context is to the make an object that holds the lifetime of the FMDB reference, and handle cleaning up resources on it's deinit(). You can then also have a function which vends a Publisher of whatever you need from that same object, and then the cancellation of the request is changed semantically to only cancelling getting the database, not cancelling and cleaning up the database connection.

Race condition in unit tests

I'm currently testing a number of classes that do network stuff like REST API calls, and a Realm database is mutated in the process. When I run all the different tests I have at once, race conditions appear (but of course, when I run them one by one, they all pass). How can I reliably make the tests pass?
I have tried to call the mentioned functions in a GCD block like this:
DispatchQueue.main.async {
self.function.start()
}
One of my tests are still failing, so I guess the above didn't work. I have enabled Thread Sanitizer and it reports, from time to time, that race conditions appear.
I can't post code, so I'm looking for conceptual solutions.
Typically some form of dependency injection. Be it an internally exposed var to the DispatchQueue, a default argument in a function with the queue, or a constructor argument. You just need some way to pass a test queue that dispatches the event when you need to.
DispatchQueue.main.async will schedule the block async to the callee on the main queue and therefore isn't guarenteed by the time you make an assertion.
Example (disclaimer: I'm typing from memory so it might not compile but it gives the idea):
// In test code.
struct TestQueue: DispatchQueue {
// make sure to impement other necessary protocol methods
func async(block: () -> Void) {
// you can even have some different behavior for when to execute the block.
// also you can pass XCTestExpectations to this TestQueue to be fulfilled if necessary.
block()
}
}
// In source code. In test, pass the Test Queue to the first argument
func doSomething(queue: DispatchQueue = DispatchQueue.main, completion: () -> Void) {
queue.async(block: completion)
}
Other methods of testing async and eliminating race conditions revolve around craftily fulfilling an XCTestExpectation.
If you have access to the completion block that is eventually invoked:
// In source
class Subject {
func doSomethingAsync(completion: () -> Void) {
...
}
}
// In test
func testDoSomethingAsync() {
let subject = Subject()
let expect = expectation(description: "does something asnyc")
subject.doSomethingAsync {
expect.fulfill()
}
wait(for: [expect], timeout: 1.0)
// assert something here
// or the wait may be good enough as it will fail if not fulfilled
}
If you don't have access to the completion block it usually means finding a way to inject or subclass a test double that you can set an XCTestExpectation on and will eventually fulfill the expectation when the async work has completed.

All asynchronous calls succeed or none, how to handle

I'm trying to create an online mobile application and can't figure out the best way to handle functions with multiple asynchronous calls. Say I have a function for example that updates a user in some way, but involved multiple asynchronous calls in the single function call. So for example:
// Function caller
update(myUser) { (updatedUser, error) in
if let error = error {
// Present some error UI to the user
}
if let updatedUser = updatedUser {
// Do something with the user
}
}
// Function implementation
public func updateUser(user: User, completion: #escaping (User?, Error?) -> () {
// asynchronous call A
updateUserTable(user: User) { error in
if let error = error {
completion(nil, error)
} else {
// create some new user object
completion(user, nil)
}
}
// asynchronous call B
uploadMediaForUser(user: User) { error in
if let error = error {
completion(nil, error)
}
}
// asynchronous call C
removeOldReferenceForUser(user: User) { error in
if let error = error {
completion(nil, error)
}
}
// Possibly any additional amount of asynchronous calls...
}
In a case like this, where one function call like updating a user involved multiple asynchronous calls, is this an all or nothing situation? Say for example the updateUserTable() call completes, but the user disconnects from the internet as uploadMediaForUser() was running, and that throws an error. Since updateUserTable() completed fine, my function caller thinks this method succeeded when in fact not everything involved in updating the user completed. Now I'm stuck with a user that might have mismatched references or wrong information in my database because the user's connection dropped mid call.
How do I handle this all or nothing case? If EVERY asynchronous call completed without an error, I know updating the user was a success. If only a partial amount of asynchronous calls succeeded and some failed, this is BAD and I need to either undo the changes that succeeded or attempt the failed methods again.
What do I do in this scenario? And also, and how do I use my completion closure to help identify the actions needed depending on the success or failure of the method. Did all them succeed? Good, tell the user. Do some succeed and some failed? Bad, revert changes or try again (i dont know)??
Edit:
Just calling my completion with the error doesn't seem like enough. Sure the user sees that something failed, but that doesn't help with the application knowing the steps needed to fix the damage where partial changes were made.
I would suggest adding helper enums for your tasks and returned result, things like (User?, Error?) have a small ambiguity of the case when for example both are nil? or you have the User and the Error set, is it a success or not?
Regarding the all succeeded or some failed - I would suggest using the DispatchGroup to notify when all tasks finished (and check how they finished in the end).
Also from you current code, when some request fails it's not clear for which user - as you pass nil, so it might bring difficulties in rolling it back after failure.
So in my point of view something like below (not tested the code, but think you should catch the idea from it) could give you control about the issues you described:
public enum UpdateTask {
case userTable
case mediaUpload
// ... any more tasks you need
}
public enum UpdateResult {
case success
case error([UpdateTask: Error])
}
// Function implementation
public func updateUser(user: User, completion: #escaping (User, UpdateResult) -> ()) {
let updateGroup = DispatchGroup()
var tasksErrors = [UpdateTask: Error]()
// asynchronous call A
updateGroup.enter()
updateUserTable(user: User) { error in
if let error = error {
tasksErrors[.userTable] = error
}
updateGroup.leave()
}
// ... any other similar tasks here
updateGroup.notify(queue: DispatchQueue.global()) { // Choose the Queue that suits your needs here by yourself
if tasksErrors.isEmpty {
completion(user, .success)
} else {
completion(user, .error(tasksErrors))
}
}
}
Keep a “previous” version of everything changed, then if something failed revert back to the “previous” versions. Only change UI once all returned without failure, and if one failed, revert to “previous” version.
EX:
var temporary = “userName”
getChanges(fromUser) {
If error {
userName = temporary //This reverts back due to failure.
}
}

How to test asynchronous method results?

When we get table view datasource, we will ask a network request. It is asynchronous. I have no idea to test the result operation. There is a method to get the points.
func loadPoints() {
API().retrievePoints{ [weak self](pointsSet, error) in
DispatchQueue.main.async(execute: {
// Make sure the call succeeded; return an error if it didn't
guard error == nil else {
self?.showErrorMessage()
Device.debugLog(item:"Error loading Points: \(String(describing: error))")
return
}
self?.pointsSet = pointsSet
self?.tableView.reloadData()
})
}
}
I have known that if we want to test the retrievePoints method, we can test like bellow
//points
func testRetrievePoints() {
let expectation = self.expectation(description: "RetrievePoints")
API().retrievePoints{ (pointsSet, error) -> Void in
XCTAssertNil(pointsSet)
XCTAssertNotNil(error)
expectation.fulfill()
}
waitForExpectations(timeout: 15.0, handler: nil)
}
Now I want to test the codes
self?.pointsSet = pointsSet
self?.tableView.reloadData()
self?.showErrorMessage()
For now I just use sleep(15) to wait the method. But it is inaccurate.
Could you please help me? Thanks in advance.
Just as what you have said, it's asynchronous. So it will take time before finish. That is to say that you need to wait before it can success.
Also note that it's just timeout value. All your task must finish within this value. Or it will be treated as failure.
You want to test your data source — not your web service.
there for you should mock the api call.
To achieve this you could use a mocking framework. But I'd rather go another route:
create a protocol that declares the public interface of API, let API conform to that protocol
pass the API as a dependency into the data source. either as an init parameter or via a property. Passing objects is easier that classes, I'd change retrievePoints to be an instance method.
For your test write an APIMock that implements the protocol. Let retrievePoints' callback return prepared points.
Now the points will be returned immediately, no need for timeouts. If you want to defer that your mock can use a DispatchQueue.main.asyncAfter call.

proper use of Alamofire queue

Here is the scenario, everything works but I get hanged up on the main queue. I have:
singleton class to manage API connection. Everything works (execution time aside....)
a number of view controllers calling GET API via the above singleton class to get the data
I normally call the above from either viewDidLoad or viewWillAppear
they all work BUT ....
if I call a couple of API methods implemented with Alamofire.request() with a closure (well, I need to know when it is
time to reload!), one of the two gets hung waiting for the default
(main) queue to give it a thread and it can take up to 20 seconds
if I call only one, do my thing and then call a POST API, this
latter one ends up in the same situation as (5), it takes a long
time to grab a slot in the default queue.
I am not specifying a queue in Alamofiore.request() and it sounds to me like I should so I tried it. I added a custom concurrent queue to my singleton API class and I tried adding that to my Alamofiore.request() .....and that did absolutely nothing. Help please, I must be missing something obvious?!
Here is my singleton API manager (excerpt) class:
class APIManager {
// bunch of stuff here
static let sharedInstance = APIController()
// more stuff here
let queue = DispatchQueue(label: "com.teammate.response-queue", qos: .utility, attributes: [.concurrent])
// more stuff
func loadSports(completion: #escaping (Error?) -> Void) {
let parameters: [String: Any?] = [:]
let headers = getAuthenticationHeaders()
let url = api_server+api_sport_list
Alamofire.request(url, method: .get, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseString (queue: queue) { response in
if let json = response.result.value {
if let r = JSONDeserializer<Response<[Sport]>>.deserializeFrom(json: json) {
if r.status == 200 {
switch r.content{
case let content as [Sport]:
self.sports = content
NSLog("sports loaded")
completion(nil)
default:
NSLog("could not read the sport list payload!")
completion(GenericError.reportError("could not read sports payload"))
}
}
else {
NSLog("sports not obtained, error %d %#",r.status, r.error)
completion(GenericError.reportError(r.error))
}
}
}
}
}
// more stuff
}
And this is how I call the methods from APIManager once I get the sigleton:
api.loadSports(){ error in
if error != nil {
// something bad happened, more code here to handle the error
}
else {
self.someViewThingy.reloadData()
}
}
Again, it all works it is just that if I make multiple Alamofire calls from the same UIViewController, the first is fast, every other call sits for ever to get a spot in the queue an run.
UI updates must happen on the main queue, so by moving this stuff to a concurrent queue is only going to introduce problems. In fact, if you change the completion handler queue to your concurrent queue and neglect to dispatch UI updates back to the main queue, it's going to just make it look much slower than it really is.
I actually suspect you misunderstand the purpose of the queue parameter of responseString. It isn't how the requests are processed (they already happen concurrently with respect to the main queue), but merely on which queue the completion handler will be run.
So, a couple of thoughts:
If you're going to use your own queue, make sure to dispatch UI updates to the main queue.
If you're going to use your own queue and you're going to update your model, make sure to synchronize those updates with any interaction you might be doing on the main queue. Either create a synchronization queue for that or, easier, dispatch all model updates back to the main queue.
I see nothing here that justifies the overhead and hassle of running the completion handler on anything other than the main queue. If you don't supply a queue to responseString, it will use the main queue for the completion handlers (but won't block anything, either), and it solves the prior two issues.