How to deal with thread and Realm ? (iOS) - ios

I use Realm to store my model objects. In my object I have a function which generate NSData from its own properties values. This generation can be long, So I would like to generate my NSData in a thread with handler block.
My problem is that Realm data access is only possible on the Realm creation entity (actually the main thread). So when I access to my RealmObject properties in a thread, application crash. According to Realm specs, it's normal. But what is the best solution to make my NSData generation in a thread depending to Realm limitation ?
Actually I have two ideas :
make a Realm specific dispatch queue and make all my Realm access write in this queue
get all properties in need in a temp struct(or a set of variables) and work with this struct/variables to generate my NSData in a thread.
I assume that a lot of Realm users need to deal with threads and Realm, so what did you do in this kind of case ?

Pass the object id to the code that is running in the separate thread. Within that thread create a Realm instance (let realm = try! Realm()) and retrieve your object. Then you can do your long generation and return the result with a callback.
let objectId = "something"
dispatch_async(queue) {
let realm = try! Realm()
let myObject = realm.objectForPrimaryKey(MyObject.self, key: objectId)
let result = myObject.longOperation()
// call back with results
}
or
let objectRef = ThreadSafeReference(to: myObject)
DispatchQueue(label: "background").async {
autoreleasepool {
let realm = try! Realm()
guard let myObject = realm.resolve(objectRef) else {
return // object was deleted
}
let result = myObject.longOperation()
// call back with results
}
}

Related

How to init ManagedObjectContext on the right queue?

Extremely need an advice, currently run out of ideas. I stack with core data concurrency related issue, to debug I use -"com.apple.CoreData.ConcurrencyDebug" and what I have:
Stack:
Thread 3 Queue: coredata(serial)
0 +[NSManagedObjectContext Multithreading_Violation_AllThatIsLeftToUsIsHonor]:
CoreData`-[NSManagedObjectContext executeFetchRequest:error:]:
1 -[NSManagedObjectContext executeFetchRequest:error:]:
2 NSManagedObjectContext.fetch (__ObjC.NSFetchRequest) throws -> Swift.Array:
3 AppDelegate.(fetchRequest NSFetchRequest) -> [A]).(closure #1)
I get into AppDelegate::fetchRequest from here:
let messageRequest: NSFetchRequest<ZMessage> = ZMessage.fetchRequest();
messageRequest.sortDescriptors = [NSSortDescriptor(key: "id", ascending: false)];
let messageArray: Array<ZMessage> = self.fetchRequest(messageRequest);
I perform all coredata stuff on the serial queue(self.queueContainer).
public func fetchRequest<T>(_ request: NSFetchRequest<T>) -> Array<T>
{
var retval: Array<T> = Array<T>();
self.queueContainer.sync {
do {
retval = try self.persistentContainer.viewContext.fetch(request);
} catch {
let nserror = error as NSError;
fatalError("[CoreData] Unresolved fetch error \(nserror), \(nserror.userInfo)");
}
}
return retval;
}
This is what I found useful.
Below are some of the rules that must be followed if you do not want your app that uses CoreData to crash (or) corrupt the database:
A NSManagedObjectContext should be used only on the queue that is associated with it.
If initialized with .PrivateQueueConcurrencyType, a private, internal queue is created that is associated with the object. This queue can be accessed by instance methods .performBlockAndWait (for sync ops) and .performBlock (for async ops)
If initialized with .MainQueueConcurrencyType, the object can be used only on the main queue. The same instance methods ( performBlock and performBlockAndQueue) can be used here as well.
An NSManagedObject should not be used outside the thread in which it is initialized
Now I'm digging around but honestly speaking can't be sure that my managed object context(MOC) is associated with the right queue.
From manual:
...A consequence of this is that a context assumes the default owner is the thread or queue that allocated it—this is determined by the thread that calls its init method.
In the AppDelegate I do not operate with MOC directly, instead of this I instantiating NSPersistentContainer which owns this MOC. Just in case I'm doing this on the same serial queue as well.
public lazy var persistentContainer: NSPersistentContainer =
{
self.queueContainer.sync {
let container = NSPersistentContainer(name: "Joker")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}
}()
Thanks in advance.
I am not a Swift coder , but What is queueContainer?
You should not do the threading yourself, you should use the NSManagedObjectContext block methods as you wrote in your quotation:
The same instance methods ( performBlock and performBlockAndQueue) can
be used here as well.
managedObjectContext.performBlock {
Whatever managedObjectContext you are using, you should use that context block method and do your stuff inside the block methods.
Look at the documentation here for examples on how to do this correctly.
Also to avoid crashes and threading errors:
NSManagedObject instances are not intended to be passed between
queues. Doing so can result in corruption of the data and termination
of the application. When it is necessary to hand off a managed object
reference from one queue to another, it must be done through
NSManagedObjectID instances.
You retrieve the managed object ID of a managed object by calling the
objectID method on the NSManagedObject instance.

terminating with uncaught exception of type realm::IncorrectThreadException: Realm accessed from incorrect thread

am using Realm for my persistence. Now I really confused should i use realm or not.
I am using almofire ,ObjectMapper and Realm following is my code.
UserInfo
class UserInfo:Object,Mapper
{
dynamic var name:String?
dynamic var accountTye:String?
dynamic var loginResult: String?
}
Once I get success response from server for my login.
=========
UserSessionManager
var userInfo:UserInfo?
UserSessionManager.shared().saveSession()
func saveSession()
{
RealmHelper.shared().save(userInfo!)
}
RealmHelper
func save<T:Object>(_ realmObject:T) {
let backgroundQueue = DispatchQueue(label: ".realm", qos: .background)
backgroundQueue.async {
let realm = try! Realm()
debugPrint("Realm Creation (Thread.current)") I am getting same thread
if realm.isInWriteTransaction{
return
}
try! realm.write {
debugPrint("Realm add (Thread.current)") I am getting same thread
realm.add(realmObject)
}
}
}
When I am trying to accessing UserSessionManager.shared().userInfo object I am getting this crash.
I did not understand why it is happening.
I have fixed this issue by accessing the object UserInfo from Realm.
The problem is I am adding realm object(UserInfo) into Realm.
I am trying to access the same object which is stored in another singleton class directly which is causing me the issue.
Now I am accessing the same object after retriving from realm , now no issues,
But I don't is it the way to access Realm object once we added it to Realm.

Design pattern for Realm persistence

I'm using Realm in a App and I'm trying to abstract much as possible so that in the future I can swap database providers without too much change.
This pattern has worked well although I'm concerned about the following.
Is creating a new realm object everytime a overhead (My current understanding is that Realm Objects are cached internally)?
Are there any problems with the way I'm using Realm ?
Are there any better design patterns for my purpose ?
public struct BookDataLayer: BookDataLayerProvider {
func isBookAvailable(bookIdentifier: String) throws -> Bool {
let database = try getDatabase()
return !database.objects(Book).filter("identifier = %#", bookIdentifier).isEmpty
}
func createOrUpdateBook(bookIdentifier: String, sortIndex: Int) throws {
let book = Book()
Book.bookIdentifier = bookIdentifier
Book.sortIndex = sortIndex
try create(book, update: true)
}}
protocol BookDataLayerProvider : DataAccessLayer {
func isBookAvailable(bookIdentifier: String) throws -> Bool
func createOrUpdateBook(bookIdentifier: String, sortIndex: Int) throws n}
extension DataAccessLayer {
func getDatabase() throws -> Realm {
do {
let realm = try Realm()
// Advance the transaction to the most recent state
realm.refresh()
return realm
} catch {
throw DataAccessError.DatastoreConnectionError
}
}
func create(object: Object, update: Bool = false) throws {
let database = try self.getDatabase()
do {
database.beginWrite()
database.add(object, update: update)
// Commit the write transaction
// to make this data available to other threads
try database.commitWrite()
} catch {
throw DataAccessError.InsertError
}
}}
// Usage
let bookDataLayer = BookDataLayer()
bookDataLayer.isBookAvailable("4557788")
bookDataLayer.createOrUpdateBook("45578899", 10)
That's a completely solid design pattern. It's pretty common for developers to abstract the data layer APIs way from their code in case they need to switch it out.
In response to your questions:
You're correct. Realm object instances are internally cached, so you can easily call let realm = try! Realm() multiple times with very little overhead.
Unless you've found a specific reason, it's probably not necessary to call refresh() on the Realm instance every time you use it. Realm instances on the main thread are automatically refreshed on each iteration of the run loop, so you only need to call refresh() if you're expecting changes on a background thread, or need to access changes before the current run loop has completed.
'Better' design patterns is probably a matter of opinion, but from what I've seen from other codebases, what you've got there is already great! :)

How to access NSManagedObject entity using objectID which is not saved yet in iOS?

I have created one entity using private queue MOC and called performBlock() to save it asynchronously.
Let’s say I want this newly created entity in other context / MOC, so I have only one way to access it is use object id.
But as it’s asynchronous call to save entity, it is possibility of situation where entity is not saved yet and I want to access it immediately after performBlock(), but on other context.
What is the way to access it in this situation?
Thanks in advance!
Update
let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateMOC.parentContext = mainThreadManagedObjectContext()
let newUser = Contact(nameStr: "name", moc: privateMOC)
This is my generic code to create new entities like:
init(nameStr: String, moc:NSManagedObjectContext) {
let mEntity = NSEntityDescription.entityForName("Contact", inManagedObjectContext: moc)
super.init(entity: mEntity!, insertIntoManagedObjectContext: moc)
name = nameStr
}
After creating, I am saving it immediately as:
performBlock { () -> Void in
do {
try privateMOC.save()
}catch {
fatalError("Error asynchronously saving private moc - \(errorMsg): \(error)")
}
}
//accessing
let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
privateMOC.parentContext = mainThreadManagedObjectContext()
let pred = NSPredicate(format: "name == %#", "name")
if let results = DatabaseInterface.sharedInstance.fetchEntityFromCollection("Contact", withPredicate: pred, moc: privateMOC) {
if let user = results.last {
return user as! Contact
}
}
else {
DDLogWarn("No user found")
}
I am not getting any user, but I can see this new contact is saved in DB when look using sqlitebrowser
Forgive me for indentation of code :(
What you're asking is impossible.
When you create a managed object on one context, it only exists in memory until you save changes. No other managed object context can get to it, even if you use the object ID. The other context can't look up the data from the persistent store file, and it can't get it from the other context. It can't get the object at all. You need to wait until the save completes.
A couple of things you might try, depending on how your app is structured:
Use performBlockAndWait instead of performBlock, so that you'll know that the save has completed by the time the block finishes.
Keep using performBlock but put calls inside that block that lead to accessing the object on a different context-- via a nested performBlock call on that other context.

Realm and Parse integration: how to save the realm

I'm trying to create a bundle realm for my application. I thought it should be quite simple. Since I already have all needed records in Parse, I just have to:
create realm models and objects
load parse records to realm objects
save the realm
So, I created two realm models:
class RealmContainer : Object {
dynamic var rContainerId: String! //should be equal objectId from Parse
dynamic var rContainerName: String! //should be equal "name" field from Parse
...
var messages = List<RealmMessage>()
override static func primaryKey() -> String? {
return "rContainerId"
}
}
and
class RealmMessage : Object {
dynamic var rMessageId: String!
...
dynamic var rParentContainer: RealmContainer!
}
Getting results from Parse seems to be working. Also my realm objects are also good
var allUserContainers: [RealmContainer] = []
I was able to populate this array with values from Parse. But when I'm trying to save this realm, I'm getting a) nothing or b) error message
My code (this one'll get nothing):
let realm = try! Realm()
try! realm.write {
realm.add(self.allUserContainers[0])
print(Realm().path)
print(realm.path)
}
My code (this one'll get nothing too):
let realm = try! Realm()
try! realm.write {
realm.create(RealmContainer.self, value: self.allUserContainers[0], update: true)
print(Realm().path)
print(realm.path)
}
My code 3 (this will get me an error message "Terminating app due to uncaught exception 'RLMException', reason: 'Illegal recursive call of +[RLMSchema sharedSchema]. Note: Properties of Swift Object classes must not be prepopulated with queried results from a Realm"):
//from firstViewController, realm is a global variable
let realm = try! Realm()
//another swift module
try! realm.write {
realm.create(RealmContainer.self, value: self.allUserContainers[0], update: true)
print(Realm().path)
print(realm.path)
}
Obviously I don't properly understand how it should work, but I tried several swift/realm tutorials and they were actually straightforward. So, what did I do wrong?
Update
So, I updated my code to make it as much simple/readable as possible. I have a Dog class, and I am trying to get Dogs from Parse and put them to Realm.
AppDelegate.swift
let realm = try! Realm() //global
Dog.swift
class Dog : Object {
dynamic var name = ""
dynamic var age = 0
}
User.swift (getDogs and putDogs functions)
class User {
var newDogs:[Dog] = []
...
func getDogs() {
self.newDogs = []
let dogsQuery = PFQuery(className: "Dogs")
dogsQuery.limit = 100
dogsQuery.findObjectsInBackgroundWithBlock { (currentModes, error) -> Void in
if error == nil {
let tempModes:[PFObject] = currentModes as [PFObject]!
for var i = 0; i < tempModes.count; i++ {
let temp = Dog()
temp.name = currentModes![i]["dogName"] as! String
self.newDogs.append(temp)
}
} else {
print("something happened")
}
}
}
...
func putDogs() {
print(self.newDogs.count)
try! realm.write({ () -> Void in
for var i = 0; i < newDogs.count; i++ {
realm.add(newDogs[i])
}
})
try! realm.commitWrite() //doesn't change anything
}
error message still the same:
Terminating app due to uncaught exception 'RLMException', reason:
'Illegal recursive call of +[RLMSchema sharedSchema]. Note: Properties
of Swift Object classes must not be prepopulated with queried
results from a Realm
I believe I just have some global misunderstanding about how Realm is working because it is extremely simple configuration.
About your RealmSwift code : You have implemented it right.
When you declare a class of realm in swift, it's a subclass of Object class. Similarly for the parse it's subclass of PFObject.
You custom class have to have only one base class. So can't use functionalities of both the libraries Parse as well as Realm.
If you have to have use both Parse and Realm, you need to declare two different classes like RealmMessage and ParseMessage.
Retrieve data for ParseMessage from parse and copy properties to RealmMessage.
Why you want to use both Parse and Realm ?
parse also provides local data store by just writing Parse.enableLocalDatastore() in appDelegate before
Parse.setApplicationId("key",
clientKey: "key")
Your Realm Swift code seems fine. Realm is very flexible when creating objects using (_:value:update:), in being able to take in any type of object that supports subscripts. So you could even directly insert PFObject if the schemas matched.
The problem seems to be how you're populating the allUserContainers array. As the error says, you cannot fetch a Realm Object from Realm and then try and re-add it that way. If you're trying to update an object already in Realm, as long as the primary key properties match, you don't need to supply the whole object back again.
Can you please revisit the logic of how your allUserContainers variable is being populated, and if you can't fix it, post the code into your question?
Sidenote: You probably don't need to define your Realm properties as implicitly unwrapped as well. This is the recommended pattern:
class RealmContainer : Object {
dynamic var rContainerId = ""
dynamic var rContainerName = ""
}
Actually I found what was wrong: as i suspected it was a stupid mistake, some property in my class was UIImage type, and Realm just can't work with this type (even if I'm not trying to put objects of this class into realm). So, my bad. I am slightly embarassed by it, but will not delete my question because error messages from Realm were not very understandable in this case

Resources