Swift Realm Write method is Sync or Async Thread - ios

I am using Realm in My project, and I want to know whether the realm.write() method is synchronous or not.
My example is here:
let realm = try! Realm()
try! realm.write {
realm.delete(message)
}
realm.invalidate()
In the above example, I am deleting a realm object and outside braces I am writing invalidate()
Here is my confusion:
If write() is synchronous, then invalidate() is ok
And if Async than before write invalidate will call, and realm will release but operation is running in background
Thanks

Realm.write is synchronous. It just calls realm.beginWrite()/realm.commitWrite() with some error handling:
public func write(_ block: (() throws -> Void)) throws {
beginWrite()
do {
try block()
} catch let error {
if isInWriteTransaction { cancelWrite() }
throw error
}
if isInWriteTransaction { try commitWrite() }
}

The method you write is synchronous method as you did not specify the background queue for it.
Purpose of Invalidate() method
func invalidate()
Description
Invalidates all Objects, Results, LinkingObjects, and Lists managed by the Realm.
A Realm holds a read lock on the version of the data accessed by it, so that changes made to the Realm on different threads do not modify or delete the data seen by this Realm. Calling this method releases the read lock, allowing the space used on disk to be reused by later write transactions rather than growing the file. This method should be called before performing long blocking operations on a background thread on which you previously read data from the Realm which you no longer need.
All Object, Results and List instances obtained from this Realm instance on the current thread are invalidated. Objects and Arrays cannot be used. Results will become empty. The Realm itself remains valid, and a new read transaction is implicitly begun the next time data is read from the Realm.
Calling this method multiple times in a row without reading any data from the Realm, or before ever reading any data from the Realm, is a no-op. This method may not be called on a read-only Realm.

Related

Realm iOS: how expensive is it to initiate Realm with a bundled db?

I'm using Realm for my project and I need to query a list of results in a non-UI-blocking thread (ie. background), read only; I consulted Realm's doc, it seems that I need to create the Realm instance in the same thread where it's been queried, so I wonder how expensive it is if I re-create Realm object every time?
#IBAction func scoreAction(_ sender: Any?) {
DispatchQueue.global(qos: .background).async }
let scores = loadScore()
DispatchQueue.main.async {
display(scores)
}
}
}
then:
func loadScore() -> [Score] {
let realm = try! Realm(configuration: config)
return realm.objects(Score.self).filter("some criteria")
}
Calling the initializer of Realm doesn't actually create a new database, it simply creates a new reference to the existing Realm database at the location specified in the RealmConfiguration used in the initializer of Realm. This means that in general, once the database is open, creating a new reference to it by calling Realm() or Realm(configuration: config) isn't expensive computationally. So in general, it can often make more sense to create a new reference to your Realm when switching between threads.
Of course, to know for sure which is the more optimal way for your specific use case, you'll actually need to run tests on a real device, but as long as you're not switching between threads frequently (say several times in a single second), you should be fine with creating a new reference to Realm on both threads after switching between them.

Can we generate Realm results in background queue and use it on main thread

I'm starting using Realm recently, I'm not sure if my use case is valid:
Normally, when reading a lot of data from DB, I want to put it in a background queue so it will async get the data and later use it on main thread.
For example, I want to fetch several results based on city:
private var results: [Results<SomeObject>?] = []
autoreleasepool {
DispatchQueue(label: "background").async {
[unowned self] in
do
{
let realm = try Realm()
for i in 1...City.count
{
self.results.append(realm.objects(SomeObject.self).filter("city=\(i)"))
}
}
catch
{
NSLog("Failed to open Realm instance on background qeueue")
}
}
}
And later use results to update my chart:
cell.setChartData(ChartDataFactory.createCombinedData(from: results[0]))
However if I apply this model for Realm, I'm getting error like
Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.
I understand I must use realm for each thread, and I can do this by reading realm on main thread, but I don't want the realm query block my main thread.
is there any way I can achieve my goal? e.g. reading realm in a background queue and access the results from another thread, while keeping the auto-refresh feature.
Thanks.
Realm has built-in functionality for running a query on a background thread and delivering the results to the main thread by using Results.observe().
If you specifically need to perform expensive filtering logic that can't be expressed as a Realm query, you can manually pass an array of objects between threads using ThreadSafeReference.
As of 5.0, you can now construct the query on a background thread and receive notifications on the main thread using the on: parameter to observe():
DispatchQueue.global().async {
let realm = try! Realm()
let results = realm.objects(ObjectType.self).filter("property in %#", expensiveFunction(realm))
self.token = results.observe(on: .main) { change in
// do stuff with the results on the main thread
}
}
Realm objects are only accessible through the realm from which they are fetched or created. Realm instances cannot be shared between threads (which you are aware of), and sharing an object from a specific realm instance to another thread, implicitly has the same effects as sharing a realm instance between threads. This is due to the tight coupling between the objects and the realm instance.
As mentioned in this GitHub issue https://github.com/realm/realm-cocoa/issues/946, the recommended practice is to share the primary keys (if your realm object overrides the primaryKey method of RealmObject (Objective-C) / Object (Swift)).
You're trying to directly access 'results' property from a different queue and that will crash. You should instead use ThreadSafeReference as indicated on the answer of Thomas.
Make sure to create a ThreadSafeReference for results and call realm.resolve() on your background queue before fetching from your Realm database.
I solved it like this. I see an overall performance improvement, but I couldn't find any implementation example for querying on a background thread. Might be there is a solution with even better performance.
self.results = self.realm.objects(Object.self).filter(predicate).sorted(by: sortProperties)
self.notificationToken = self.results.observe({ (notification) in
self.tableview.reloadData()
})
This is the results on an iPhone X with a database of ~171k items. Durations are in seconds.
Search on UI tread:
UI thread blocked 0.730504035949707
Search with the code from above:
UI thread blocked 0.28138411045074463
background search duration 0.5073530673980713

Realm accessed from incorrect thread - again

I noticed many problems with accessing realm object, and I thought that my solution would be solving that.
So I have written simple helping method like this:
public func write(completion: #escaping (Realm) -> ()) {
DispatchQueue(label: "realm").async {
if let realm = try? Realm() {
try? realm.write {
completion(realm)
}
}
}
}
I thought that completion block will be fine, because everytime I write object or update it, I use this method above.
Unfortunately I'm getting error:
libc++abi.dylib: terminating with uncaught exception of type realm::IncorrectThreadException: Realm accessed from incorrect thread.
Instances of Realm and Object are thread-contained. They cannot be passed between threads or that exception will occur.
Since you're passing the completion block itself to the background queue at the same time the queue is being created (As Dave Weston said), any Realm objects inside that block will most certainly not have been created on the same thread, which would explain this error.
Like Dave said, you're creating a new dispatch queue every time you call that method. But to expand upon that, there's also no guarantee by iOS that a single queue will be consistently called on the same thread.
As such, best practice with Realm is to recreate your Realm objects on the same thread each time you want to perform a new operation on that thread. Realm internally caches instances of Realm on a per-thread basis, so there's very little overhead involved with calling Realm() multiple times.
To update a specific object, you can use the new ThreadSafeReference feature to re-access the same object on a background thread.
let realm = try! Realm()
let person = Person(name: "Jane") // no primary key required
try! realm.write {
realm.add(person)
}
let personRef = ThreadSafeReference(to: person)
DispatchQueue(label: "com.example.myApp.bg").async {
let realm = try! Realm()
guard let person = realm.resolve(personRef) else {
return // person was deleted
}
try! realm.write {
person.name = "Jane Doe"
}
}
Your method creates a new DispatchQueue every time you call it.
DispatchQueue(name:"") is an initializer, not a lookup. If you want to make sure you're always on the same queue, you'll need to store a reference to that queue and dispatch to it.
You should create the queue when you setup the Realm, and store it as a property of the class that does the setup.
Perhaps it helps someone (as I spent a few hours looking for a solution)
In my case, I had a crash in background mapping of JSON to a model (which imported ObjectMapper_Realm). At the same time there was an instance of realm allocated on main thread.
Generally it happens when you initialised it in different thread and trying to access or modify from different thread. Just put a debugger to see which thread it was initialised and try to use same thread.

Having to call fetch twice from CoreData

Both on simulator and my real device, an array of strings is saved upon app termination. When I restart the app and fetchRequest for my persisted data (either from a viewDidLoad or a manual button action), I get an empty array on the first try. It isn't until the second time I fetchRequest that I finally get my data.
The funny thing is that there doesn't seem to be a time discrepancy involved in this issue. I tried setting various timeouts before trying to fetch the second time. It doesn't matter whether I wait 10 seconds to a minute -- or even immediately after the first fetch; the data is only fetched on the second try.
I'm having to use this code to fetch my data:
var results = try self.context.fetch(fetchRequest) as! [NSManagedObject]
while (results.isEmpty) {
results = try self.context.fetch(fetchRequest) as! [NSManagedObject]
}
return results
For my sanity's sake, here's a checklist:
I'm initializing the Core Data Stack using boilerplate code from Apple: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/InitializingtheCoreDataStack.html#//apple_ref/doc/uid/TP40001075-CH4-SW1
I'm putting my single DataController instance in a static variable at the top of my class private static let context: NSManagedObjectContext = DataController().managedObjectContext
I'm successfully saving my context and can retrieve the items without any issue in a single session; but upon trying to fetch on the first try in a subsequent session, I get back an empty array (and there lies the issue).
Note** I forgot to mention that I'm building a framework. I am using CoreData with the framework's bundle identifier and using the model contained in the framework, so I want to avoid having to use logic outside of the framework (other than initalizing the framework in the appDelegate).
The Core Data stack should be initialized in applicationDidFinishLaunchingWithOptions located in appDelegate.swift because the psc is added after you're trying to fetch your data.
That boilerplate code from Apple includes:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
/* ... */
do {
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)
} catch {
fatalError("Error migrating store: \(error)")
}
}
The saved data isn't available until the addPersistentStoreWithType call finishes, and that's happening asynchronously on a different queue. It'll finish at some point but your code above is executing before that happens. What you're seeing isn't surprising-- you're basically looping until the async call finishes.
You need to somehow delay your fetch until the persistent store has been loaded. There are a couple of possibilities:
Do something sort of like what you're already doing. I'd prefer to look at the persistent store coordinator's persistentStores property to see if any stores have been loaded rather than repeatedly trying to fetch.
Post a notification after the persistent store is loaded, and do your fetch when the notification happens.

Managing asynchronous calls to web API in iOS

I am fetching data (news articles) in JSON format from a web service. The fetched data needs to be converted to an Article object and that object should be stored or updated in the database. I am using Alamofire for sending requests to the server and Core Data for database management.
My approach to this was to create a DataFetcher class for fetching JSON data and converting it to Article object:
class DataFetcher {
var delegate:DataFetcherDelegate?
func fetchArticlesFromUrl(url:String, andCategory category:ArticleCategory) {
//convert json to article
//send articles to delegate
getJsonFromUrl(url) { (json:JSON?,error:NSError?) in
if error != nil {
print("An error occured while fetching json : \(error)")
}
if json != nil {
let articles = self.getArticleFromJson(json!,andCategory: category)
self.delegate?.receivedNewArticles(articles, fromCategory: category)
}
}
}
After I fetch the data I send it to DataImporter class to store it in database:
func receivedNewArticles(articles: [Article], fromCategory category:ArticleCategory) {
//update the database with new articles
//send articles to delegate
delegate?.receivedUpdatedArticles(articles, fromCategory:category)
}
The DataImporter class sends the articles to its delegate that is in my case the ViewController. This pattern was good when I had only one API call to make (that is fetchArticles), but now I need to make another call to the API for fetching categories. This call needs to be executed before the fetchArticles call in the ViewController.
This is the viewDidLoad method of my viewController:
override func viewDidLoad() {
super.viewDidLoad()
self.dataFetcher = DataFetcher()
let dataImporter = DataImporter()
dataImporter.delegate = self
self.dataFetcher?.delegate = dataImporter
self.loadCategories()
self.loadArticles()
}
My questions are:
What is the best way to ensure that one the call to the API gets executed before the other one?
Is the pattern that I implemented good since I need to make different method for different API calls?
What is the best way to ensure that one the call to the API gets executed before the other one?
If you want to ensure that two or more asynchronous functions execute sequentially, you should first remember this:
If you implement a function which calls an asynchronous function, the calling function becomes asynchronous as well.
An asynchronous function should have a means to signal the caller that it has finished.
If you look at the network function getJsonFromUrl - which is an asynchronous function - it has a completion handler parameter which is one approach to signal the caller that the underlying task (a network request) has finished.
Now, fetchArticlesFromUrl calls the asynchronous function getJsonFromUrl and thus becomes asynchronous as well. However, in your current implementation it has no means to signal the caller that its underlying task (getJsonFromUrl) has finished. So, you first need to fix this, for example, through adding an appropriate completion handler and ensuring that the completion handler will eventually be called from within the body.
The same is true for your function loadArticles and loadCategories. I assume, these are asynchronous and require a means to signal the caller that the underlying task has finished - for example, by adding a completion handler parameter.
Once you have a number of asynchronous functions, you can chain them - that is, they will be called sequentially:
Given, two asynchronous functions:
func loadCategories(completion: (AnyObject?, ErrorType?) -> ())
func loadArticles(completion: (AnyObject?, ErrorType?) -> ())
Call them as shown below:
loadCategories { (categories, error) in
if let categories = categories {
// do something with categories:
...
// Now, call loadArticles:
loadArticles { (articles, error) in
if let articles = articles {
// do something with the articles
...
} else {
// handle error:
...
}
}
} else {
// handler error
...
}
}
Is the pattern that I implemented good since I need to make different method for different API calls?
IMHO, you should not merge two functions into one where one performs the network request and the other processes the returned data. Just let them separated. The reason is, you might want to explicitly specify the "execution context" - that is, the dispatch queue, where you want the code to be executed. Usually, Core Data, CPU bound functions and network functions should not or cannot share the same dispatch queue - possibly also due to concurrency constraints. Due to this, you may want to have control over where your code executes through a parameter which specifies a dispatch queue.
If processing data may take perceivable time (e.g. > 100ms) don't hesitate and execute it asynchronously on a dedicated queue (not the main queue). Chain several asynchronous functions as shown above.
So, your code may consist of four asynchronous functions, network request 1, process data 1, network request 2, process data 2. Possibly, you need another function specifically for storing the data into Core Data.
Other hints:
Unless there's a parameter which can be set by the caller and which explicitly specifies the "execution context" (e.g. a dispatch queue) where the completion handler should be called on, it is preferred to submit the call of the completion handler on a concurrent global dispatch queue. This performs faster and avoids dead locks. This is in contrast to Alamofire that usually calls the completion handlers on the main thread per default and is prone to dead locks and also performs suboptimal. If you can configure the queue where the completion handler will be executed, please do this.
Prefere to execute functions and code on a dispatch queue which is not associated to the main thread - e.g. not the main queue. In your code, it seems, the bulk of processing the data will be executed on the main thread. Just ensure that UIKit methods will execute on the main thread.

Resources