When I delete an NSMangedObject from the database, what happens to local variables who were assigned to it?
For example, I have a simple NSManagedObject:
class MyManagedObject: NSManagedObject {
#NSManaged var name: String
}
And then in my ViewController, I pull it out of the database, and assign it locally:
class ViewController: UIViewController {
var myManagedObject: MyManagedObject!
}
Then I delete it from the database.
If print the object name I get the following in the console
print("myManagedObject.name = \(myManagedObject.name)")
//prints: "myManagedObject.name = "
As if the object isn't there? But if I turn the variable into an optional and check it for nil, I am told it's not nil.
I'm not quite sure how to reconcile this in my mind. There seems to be something pointing to the local variable, but its properties are gone.
If I have many disparate UI objects that rely on that object for its properties, I can't assume that there is some local deep copy of it in memory?
Here is more complete code:
In viewDidLoad I create the new object, save the context, fetch the object, then assign it locally.
class ViewController: UIViewController {
var myManagedObject: MyManagedObject!
override func viewDidLoad() {
super.viewDidLoad()
//1 Create the new object
let newObject = NSEntityDescription.insertNewObject(forEntityName: "MyManagedObject", into: coreDataManager.mainContext) as! MyManagedObject
newObject.name = "My First Managed Object"
//2 Save it into the context
do {
try coreDataManager.mainContext.save()
} catch {
//handle error
}
//3 Fetch it from the database
let request = NSFetchRequest<MyManagedObject>(entityName: "MyManagedObject")
do {
let saved = try coreDataManager.mainContext.fetch(request)
//4 Store it in a local variable
self.myManagedObject = saved.first
} catch {
//handle errors
}
}
}
At this point if I print the local variable's name property, I get the correct response:
print("The object's name is: \(myManagedObject.name)")
//prints: The object's name is: My First Managed Object
So, now I delete it from the database:
if let storedObject = myManagedObject {
coreDataManager.mainContext.delete(storedObject)
do {
try coreDataManager.mainContext.save()
} catch {
//handle error
}
}
But now, when I print I get the strangest output:
print("myManagedObject.name = \(myManagedObject.name)")
//prints: "myManagedObject.name = "
This is totally not the way I'm expecting memory to work. If I create a instance of a class Foo, and then pass that instance around to different objects, it's the same instance. It only goes away once no one is pointing to it.
In this case--- what is the variable, myManagedObject? It's not nil. And what is the string, name? Is it an empty string? Or is it some other weird meta-type?
The main thing what you are probably looking here for is the core data context. The context is a connection between your memory and the actual database.
Whenever you fetch the data you fetch it through context. These are managed objects which can be modified or even deleted. Still none of these really happen on the database until you save the context.
When you delete an object it is marked for deletion but it is not deleted from the memory, it must not be since if nothing else it will still be used by the context to actually delete the object from the database itself.
What happens to the managed object once you call to delete it is pretty much irrelevant, even if documented it may change as it is a part of the framework. So it is your responsibility to check these cases and to refetch the objects once needed. So you must ensure your application has a proper architecture and uses core data responsibly.
There are very many ways on how you use your database and more or less any of them has a unique way of using it optimally. You need to be more specific on what you are doing and where do you see potential issues so we can get you on the right track.
To give you an example consider data synchronization from remote server. Here you expect that data can be synchronized at any time no matter what user is doing or what part of the application he is.
For this I suggest you have a single context which operates on a separate thread. All the managed objects should be wrapped and its properties copied once retrieved from database. On most entities you would have something like:
MyEntity.findAll { items in
...the fetch happens on context thread and returns to main, items are wrappers
}
MyEntity.find(id: idString, { item in
...the fetch happens on context thread and returns to main, items are wrappers
})()
Then since you do not have any access to the managed object directly you need some kind of method to copy the data to the managed object like:
myEntityInstance.commit() // Copies all the data to core data object. The operation is done on a context thread. A callback is usually not needed
And then to save the database
MyEntity.saveDatabse {
... save happens on the context thread and callback is called on main thread
}
Now the smart part is that saveDatabse method will report to a delegate that changes have been made. A delegate is usually the current view controller so it makes sense to have a superclass like DataBaseViewController which on view did appear assigns itself as a delegate MyEntity.delegate = self, on view did load calls some method reloadData and in the databaseDidChange delegate method calls reloadData and same in viewWillAppear.
Now each of your view controllers that are subclass of DataBaseViewController will override the reloadData and in that method you will fetch the data from the database again. Either you are fetching all items or a single one. So for those singe ones you need to save the id of the object and fetch it again by that id. If the returned object is nil then item was deleted so you catch the issue you seem to be mentioning.
All of these things are oversimplified but I hope you get a basic idea about core data and how to use it. It is not easy, it never was and it most likely never will be. It is designed for speed to be able to access the data even from a very large database in shortest time possible. The result is that it might not be very safe.
Related
I'm writing an app using Realm data persistence of certain objects.
In an attempt to clean up/remodel my code (getting realm.writes out of the Views and Controllers), I tried to put them directly in the persisted object class.
The logic is basically this:
class PersistedObject: Object {
public var data: String {
get { _saved_data }
set {
do { try realm?.write { _saved_data = newValue }
} catch { print(error) }
}
}
#objc dynamic private var _saved_data = "hello there"
}
This way, I'd be able to access and rewrite realm object properties from view controllers, without needing realm.writes directly in there. That's the idea, anyway.
This works sometimes. Other times, the app crashes with the error...
"Realm accessed from incorrect thread"
...which is what I'm currently trying to solve.
This is my first iOS app and my first time using Realm.
Does it make sense to organize the code like this (I've found little in terms of support in this approach, but also generally little at all, in terms of MVC best-practices when working with Realm)
If it does make sense, how can I solve the problem with accessing Realm from the incorrect thread, while still doing the realm.writes directly in the object class?
Thanks in advance! :)
Simon
There is no sense to organize code like this. You will be able to write only from same thread it was created
to modify objects from different thread you can use ThreadSafeReference for example
You're not going to want to do that.
There's no reason not to realm.write whenever you want to write to realm - that's what it's there for. This pattern works:
// Use them like regular Swift objects
let myDog = Dog()
myDog.name = "Rex"
myDog.age = 1
// Get the default Realm
let realm = try! Realm()
// Persist your data easily
try! realm.write {
realm.add(myDog)
}
Obviously there should be better error catching in the above code.
Another downside is if you want to write 10 objects, they are written as soon as the data property is set - what if there are three vars you want to set and heep in memory before writing it? e.g. your user is creating a list of items in your app - if the user decides not to do that and hit's Cancel, you would then have to hit the database again to delete the object(s).
Consider a case where you want to write 10 objects 'at the same time'?
realm.add([obj0, obj1, obj2...])
is a lot cleaner.
Another issue comes up if you want to guarantee objects are written within a transaction - either it all succeeds or all fails. That can't be done with your current object.
The last issue is that often you'll want to instantiate an object and add some data to it, populating the object before writing to realm. With the code in the question, you're writing it as soon as data is populated. You would have to add that same code to every property.
I want to solve next problem:
I would like to work with some NSManagedObject in context and change some properties in runtime, but without telling SQLite about any changes in it.
I just want to save NSManagedObject to database when I hit save button or similar.
As I found out from source code demo we need to use beginUnsafe for this purposes (maybe I am wrong)
func unstoredWorkout() -> WorkoutEntity {
let transaction = CoreStore.beginUnsafe()
let workout = transaction.create(Into<WorkoutEntity>())
return workout
}
let workout = unstoredWorkout()
workout.muscles = []
Now when I try to update workout.muscles = [] app crashes with error:
error: Mutating a managed object 0x600003f68b60 <x-coredata://C00A3E74-AC3F-47FD-B656-CA0ECA02832F/WorkoutEntity/tC3921DAE-BA43-45CB-8271-079CC0E4821D82> (0x600001c2da90) after it has been removed from its context.
My question how we can create object without saving it and how we can save it then when we modify some properties and avoid this crash as well.
The reason for the crash is that your transaction only lives in your unstoredWorkout() method, so it calles deinit, which resets the context (and deletes all unsaved objects).
You have to retain that unsafe transaction somewhere to keep your object alive - such as in the viewcontroller that will eventually save the changes.
But I would rather encourage you to think about that if you really want to do that. You might run into other synchronization issues with various context or other async transactions alive, like when API calls are involved.
I'm currently developing an iOS app with Realm as the backend data storage. I have a class that is an RLMObject for the user profile. It stores their name, profile picture, stats, etc.
There should only always be one of these objects, however I know implementing a singleton pattern is usually a bad idea. Currently I have it implemented as
//AppDelegate.swift, applicationDidFinishLaunching
//If there's no user profiles yet (first time launch), create one
if UserProfile.allObjects().count == 0 {
let realm = RLMRealm.defaultRealm()
try! realm.transactionWithBlock() {
realm.addObject(UserProfile())
}
}
//ProfileViewController.swift
//Get the first UserProfile
var userProfile: UserProfile? {
get {
return UserProfile.allObjects().objectAtIndex(0) as? UserProfile
}
}
Is there a better way to keep track of a single instance of this class?
Your code sample uses a computed property, which will fetch the object from the Realm each time you access it.
Instead, try using a lazy var property:
lazy var userProfile: UserProfile? = {
return UserProfile.allObjects().objectAtIndex(0) as? UserProfile
}()
This type of property will load the object from the Realm only the first time it is accessed. All subsequent accesses will be directly to the object.
Note that, since UserProfile is a Realm object, its fields will automatically update in response to changes made to the underlying object in the Realm. Likewise, any changes you wish to make will need to be wrapped within a Realm write transaction.
In terms of your overall architecture, there is nothing wrong with storing a single instance of an object type in a Realm similar to what you are doing. You may want to give your UserProfile object a hardcoded constant primary key, then use the 'add or update' version of the update API (see https://realm.io/docs/swift/latest/#updating-objects). This will allow you to avoid having to explicitly create a new object.
I've just starting to using Realm and feel it's very good, fast except one thing: delete an object in Realm is easily cause an exception.
Is there any way I can delete an object in Realm safety?
In my project, I usually have to create, update, delete hundred objects on the background thread. The issue is:
If the app currently display/using one object on the main thread
In the background, I delete that object.
=> On the main thread will cause an exception when using that object's properties.
I know Realm has isInvalid method to check, but I cannot add the check in every assign properties code, it's look not good.
So, as of now, what I do is: instead of actually delete, I have a property call "deleted", and in delete, I only update that value. And on the UI, I will filtered out objects which have deleted = true
I wonder is there any way better to do this?
This is intended functionality. If a background thread deletes a Realm Object, the next time you try and access that object from any thread, an exception will be thrown.
In order to handle this, Realm provides a rich notification system that you can use to automatically receive alerts of when the contents of a Realm database have been changed and to update the UI accordingly.
If you've got a view controller that is displaying the contents of a single Realm Object, you could implement a system to be notified of any changes being made to your Realm database, and then check to make sure your object is still valid:
class MyViewController : UIViewController {
var myModel: Object = nil
var notificationToken: NotificationToken? = nil
init(model: Object) {
self.myModel = model
}
override fun viewDidLoad() {
super.viewDidLoad()
notificationToken = myModel.realm.addNotificationBlock { notification, realm in
guard myModel.invalidated == false else {
// The object has been deleted, so dismiss this view controller
}
}
}
deinit() {
notificationToken?.stop()
}
}
That notification block will be triggered each time a write transaction modified something in that particular Realm file (Even on background threads), which gives you a chance to check to see if your particular Realm Object in that UI hasn't been deleted. If it has, then you can simply dismiss the UI.
Depending on your specific needs, there is also a more fine-grained notification system you can use to specifically track changes to Realm Objects that were part of the results of a query. There's sample code for that in the Collection Notifications of the Realm documentation.
Please let me know if you need additional clarification! :)
how can I manage temporary saved CoreData?
as soon as I do something like this:
var myClass: MyClass = NSEntityDescription.insertNewObjectForEntityForName("MyClass", inManagedObjectContext: (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!) as MyClass
it is part of my managedObjectContext and will be saved when i do a context.save(nil)
Is there any way to get an object of the NSManagedObject Class without messing with my current context.
In other words: I want to have optional objects that just end up unused when I don't save them explicitly and i want to have objects that I really want to save persistently.
This answer is late but hopefully helps someone else out there. Yes there is. You create a child context. Do anything you want there. If
you want to keep your changes, save the child context - which does NOT persist data. It notifies parent context of the changes, the changes are now in your main context. You can then perform save on the main context.
let childContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
childContext.parentContext = mainContext
guard let myClass = NSEntityDescription.insertNewObjectForEntityForName("MyClass", inManagedObjectContext: childContext) as? MyClass else {
// something went wrong, handle it here
return
}
// do your thing here
to let the main context know that you want to keep your changes:
do {
try childContext.save() // does not persist anything, just lets know the parent that we are happy with the changes
} catch {
// handle error
}
when you want to NOT keep your changes you can reset:
childContext.reset()
when you want to persist the changes on the main context you do as always:
do {
try mainContext.save()
} catch {
// handle error
}
There is no direct way u can get rid of updated object what you can do is update it again with real data (which is previous data before updating). whenever you update object of 'NSManagedObject' (core data entity object) 'NSManagedObjectContext' captures all the changes and it saves whenever you do save context.
In this type of use case better to use Sqlite database.
You can delete the objects from the managed context before you call context.save, which prevents them from being committed. Assuming you have an NSManagedObjectContext called context and an instance of MyClass called myClass, then the following will work:
context.deleteObject(myClass)
See:
NSManagedObjectContext.deleteObject
Discussion
... If object has not yet been saved to a persistent store, it is simply removed from the receiver.
Of course the decision about which objects to save and which to discard are up to the logic of your application. So before you call save, you need to check all of the inserted, but not yet, committed instances that are registered with the NSManagedObjectContext you're using. You can keep track of that in your application, or perhaps this will be of use:
NSManagedObjectContext.insertedObjects
The set of objects that have been inserted into the receiver but not yet saved in a persistent store. (read-only)