How to call every struct method inside write transaction - swift

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.

Related

addSnapShotListener triggers all the function dependent to it?

I understand that when the data changes in FireStore, AddsnapshotListener gets triggered in realtime.
However, I'm not sure which function gets triggered by that, in the below code.
Is it only function C that gets triggered? or all the functions?
If all functions run, what happens to the argument of the function A??
func C() {
print("C")
deckDocRef.addSnapshotListener { snapShot, err in
...
}
}
func B() {
print("func B")
C()
}
func A(a: Bool) {
if a {
print("a")
B()
} else {
print("b")
}
}
When the data changed (and at the initial load) only the code inside your listener (the ... in your question) is called. There is no effect on functions A or B. So any code that needs the data from the database, has to be inside the snapshot callback, be called from there, or be otherwise synchronized with that code.
If this is surprising to you, you may be new to dealing with asynchronous callbacks. If that's the case, I recommend checking out:
How do I save data from cloud firestore to a variable in swift?
Storing asynchronous Cloud Firestore query results in Swift
SwiftUI - Wait until Firestore getDocuments() is finished before moving on
How can I change the order of functions triggered?

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.

Realm notifications registration while in write transaction

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
})

Wait for Parse Async functions to complete in Swift

I'm trying to wait for Parse async functions in Swift to reload my UITableView
I'm not sure if Completion Handler is useful in this case. or Dispatch Async.
I'm really confused ! Can someone help out with this
var posts = [PFObject]()
for post in posts {
post.fetchInBackground()
}
tableView.reloadData() // I want to execute that when the async functions have finished execution
You want to use fetchAllInBackground:Block I've had issues launching a bunch of parse calls in a loop where it will take a lot longer to return all of them than expected.
fetch documentation
It should look something like this:
PFObject.fetchAllInBackground(posts, block: { (complete, error) in
if (error == nil && complete) {
self.tableView.reloadData()
}
})
One thing to note is that in your example posts are empty and a generic PFObject. I'm assuming this is just for the example. Otherwise if you want to get all posts in Parse (as opposed to updating current ones) you will want to use PFQuery instead of fetching. query documentation
You need to use fetchInBackgroundWithBlock. Alternatively, if you want to wait until all have loaded and then update the UI, use PFObject's +fetchAllInBackground:block:. Note that this is a class method, and would therefore be called as PFObject.fetchAllInBackground(.... See documentation here.
Either way, because you're running in a background thread, you must update the UI on the main thread. This is normally done using dispatch_async.
The other thing to watch out for is if you run fetchInBackgroundWithBlock in a loop and collect all the results in an array, arrays are not thread safe. You will have to use something like dispatch_barrier or your own synchronous queue to synchronise access to the array. Code for the second option is below:
// Declared once and shared by each call (set your own name)...
let queue = dispatch_queue_create("my.own.queue", nil)
// For each call...
dispatch_sync(queue) {
self.myArray.append(myElement)
}
Here's a little class I made to help with coordination of asynchronous processes:
class CompletionBlock
{
var completionCode:()->()
init?(_ execute:()->() )
{ completionCode = execute }
func deferred() {}
deinit
{ completionCode() }
}
The trick is to create an instance of CompletionBlock with the code you want to execute after the last asynchronous block and make a reference to the object inside the closures.
let reloadTable = CompletionBlock({ self.tableView.reloadData() })
var posts = [PFObject]()
for post in posts
{
post.fetchInBackground(){ reloadTable.deferred() }
}
The object will remain "alive" until the last capture goes out of scope. Then the object itself will go out of scope and its deinit will be called executing your finalization code at that point.
Here is an example of using fetchInBackgroundWithBlock which reloads a tableView upon completion
var myArray = [String]()
func fetchData() {
let userQuery: PFQuery = PFUser.query()!
userQuery.findObjectsInBackgroundWithBlock({
(users, error) -> Void in
var userData = users!
if error == nil {
if userData.count >= 1 {
for i in 0...users!.count-1 {
self.myArray.append(userData[i].valueForKey("dataColumnInParse") as! String)
}
}
self.tableView.reloadData()
} else {
print(error)
}
})
}
My example is a query on the user class but you get the idea...
I have experimented a bit with the blocks and they seem to get called on the main thread, which means that any UI changes can be made there. The code I have used to test looks something like this:
func reloadPosts() {
PFObject.fetchAllIfNeededInBackground(posts) {
[unowned self] (result, error) in
if let err = error {
self.displayError(err)
}
self.tableView.reloadData()
}
}
if you are in doubt about whether or not the block is called on the main thread you can use the NSThread class to check for this
print(NSThread.currentThread().isMainThread)
And if you want it to be bulletproof you can wrap your reloadData inside dispatch_block_tto ensure it is on the main thread
Edit:
The documentation doesn't state anywhere if the block is executed on the main thread, but the source code is pretty clear that it does
+ (void)fetchAllIfNeededInBackground:(NSArray *)objects block:(PFArrayResultBlock)block {
[[self fetchAllIfNeededInBackground:objects] thenCallBackOnMainThreadAsync:block];
}

Swift Completion Block

I want to achieve the following :
In classB, to reload my database after adding 1 object. reloadDatabase() is called within the completionBlock.
In classB, reloadDatabase() will call getObjects() in classA to get the most updated list of database objects and pass to objectList in classB
Question: How do i ensure that whenever i call getObjectList() in classB, i will always get the most updated list? From my understanding, my objectList might not be update in reloadDatabase() block. I could be calling getObjectList() when reloadDatabase() haven't reach the completion block yet (objectList is still the old objectList).
I am pretty new to closures and blocks. Any guidance is greatly appreciated!
class classA: NSObject {
func addThisObject(object: RLMObject, completionBlock: () -> ())){
...
completionBlock()
}
func getObjects (completionBlock: ((list: [RLMObject]) -> ())){
var recordList = [RLMObject]()
...
completionBlock(list: recordList)
}
}
class classB: NSObject {
var objectList = [RLMObject]()
func addObject (object: RLMObject) {
classA().addThisObject(object, completionBlock: {() -> () in
self.reloadDatabase()
})
}
func reloadDatabase() {
classA().getObjects{(list) -> () in
self.objectList = list
}
}
func getObjectList() -> [RLMObject] {
return objectList
}
}
From your snippets it seems to me there is no asynchronous calls, so you won't be able to call getObjectList() before reloadDatabase() block. Closures are not asynchronous if you don't use them with something that is (e.g. GCD).
If you have asynchronous calls, but they are not in the snippets, then getObjectList() can be called while reloadDatabase() is being executed. Then you have few options:
Remove asynchronous calls
Use serial queue for your methods
Add boolean variable updateInProgress and check it in getObjectList() - how to do it
Ignore the fact that data may be outdated - it is correctness vs speed trade.
Let your database inform its clients that something changed
In your question, you don't say whether you'll be calling any of these functions from different threads. So when you call addObject() in classB, execution wouldn't even continue until the database has been reloaded and objectList was updated.
Using closures and blocks does not automatically imply that code will be executed on a different context.