Let's have this example:
We work with a set of animals. Let's assume that we don't need all animals persisted in the database, and there is a logic that controls adding and removing animals from database.
For example I have an object, a Horse, with string property name with "Suey", and I add her to Realm, with addObject:.
So now we have this object saved to Realm. This object is valid, and we can do horse.name, which will return "Suey".
What happens next is that I need to remove Suey from DB. I do deleteObject:. But sadly, it removes not only the persisted info about the horse, but also made my object invalid. Suey is basically dead, so if I want her back in the DB I need to create another horse from ground up, and only after that I will be able to have another Suey in the DB.
Is there another way of keeping Suey alive, so I have more flexibility on managing her state?
I hope I made the point clear, please let me know if you have any questions.
Thanks in advance!
UPDATE
The Horse IS engaged in one-to-many relationship, so it's life span also impacts properties in related models.
Objects in Realm are accessors for the equivalent object int the database. If for whatever reason, you need to "detach" the object variable from its on-disk representation, you can copy it into memory. An easy way to do that is to create a new, un-persisted object with the contents of the persisted one:
// Assuming `Horse` inherits from `RLMObject` and `persistedHorse` is attached to an `RLMRealm`.
Horse *persistedHorse = ...;
Horse *inMemoryHorseCopy = [[Horse alloc] initWithObject:persistedHorse];
// This will create a new `Horse` object, not tied to an RLMRealm,
// by copying the contents of `persistedHorse`.
You can use the same approach whenever you need to copy an existing RLMObject either into a stand-alone object (as above), or into another realm.
According to the documentation every modification of your query result object (in your case Object Horse with Name Suey) modifies the data on disk directly because the result is the actual data and not a copy of it. So if you remove the horse you have to create a new one.
Related
I'm sorry the title may mislead you, since I'm not so good at English. Let me describe my problem as below (You may skip to the TL;DR version at the bottom of this question).
In Coredata, I design a Product entity. In app, I download products from a server. It return JSON string, I defragment it then save to CoreData.
After sometimes has passed, I search a product from that server again, having some interaction with server. Now, I call the online product XProduct. This product may not exist in CoreData, and I also don't want to save it to CoreData since it may not belong to this system (it come from other warehouse, not my current warehouse).
Assume this XProduct has the same properties as Product, but not belong to CoreData, the developer from before has designed another Object, the XProduct, and copy everything (the code) from Product. Wow. The another difference between these two is, XProduct has some method to interact with server, like: - (void)updateStock:(NSInteger)qty;
Now, I want to upgrade the Product properties, I'll have to update the XProduct also. And I have to use these two separately, like:
id product = anArrayContainsProducts[indexPath.row];
if ([product isKindOfClass:[XProduct class]] {
// Some stuff with the xproduct
}
else {
// Probably the same display to the cell.
}
TL;DR
Basically, I want to create a scenario like this:
Get data from server.
Check existed in CoreData.
2 == true => add to array (also may update some data from server).
2 == false => create object (contains same structure as NSManagedObject from JSON dictionary => add to array.
The object created in step 4 will never exist in CoreData.
Questions
How can I create an NSManagedObject without having it add to NSMangedObjectContext and make sure the app would run fine?
If 1 is not encouragement, please suggest me a better approach to this. I really don't like to duplicate so many codes like that.
Update
I was thinking about inheritance (XProduct : Product) but it still make XProduct the subclass of NSManagedObject, so I don't think that is a good approach.
There are a couple of possibilities that might work.
One is just to create the managed objects but not insert them into a context. When you create a managed object, the context argument is allowed to be nil. For example, calling insertNewObjectForEntityForName(_:inManagedObjectContext:) with no context. That gives you an instance of the managed object that's not going to be saved. They have the same lifetime as any other object.
Another is to use a second Core Data stack for these objects, with an in-memory persistent store. If you use NSInMemoryStoreType when adding the persistent store (instead of NSSQLiteStoreType), you get a complete, working Core Data stack. Except that when you save changes, they only get saved in memory. It's not really persistent, since it disappears when the app exits, but aside from that it's exactly the same as any other Core Data stack.
I'd probably use the second approach, especially if these objects have any relationships, but either should work.
I'm new to core data and had a query.
If I call executeFetchRequest:error: to retrieve an entity from the context, and store this entity in a variable called A, and I repeat the process and store it the next time in a variable called B, will A and B refer to the same instance of the NSManagedObject i.e. will a change made to object A also be made to object B?
In addition, assuming I proceed to delete the entity from the managed object context, what would happen to these references?
Take a look at the Core Data Programming Guide section on Faulting and Uniquing. To quote:
Uniquing Ensures a Single Managed Object per Record per Context
Core Data ensures that—in a given managed object context—an entry in a persistent store is associated with only one managed object. The technique is known as uniquing. Without uniquing, you might end up with a context maintaining more than one object to represent a given record.
So, provided you execute the fetches on the same context, the returned results will point to the same instances.
When you delete an object, it is flagged for deletion until the next save operation, at which point it is deleted from the store. If you retain a reference to it thereafter, CoreData will throw an error if you try to access it. From the same document, in the section on creating and deleting objects:
You can find out if a managed object has been marked for deletion by sending it an isDeleted message. If the return value is YES, this means that the object will be deleted during the next save operation, or put another way, that the object is marked deleted for the current (pending) transaction.
the NSManagedObject subclasses are created and handled by the NSManagedContext by itself. It means you might possibly get the same instance of the NSManagedObject when you fetch them more time (but you don't know for sure). If you want to compare 2 objects, you should use some of your "isEqualTo:" implementations.
When you delete the object in that context, the contents of your properties will be removed. That means they will be set to nil if they are weak or they will point to deallocated memory if they are string. So it might happen that you app crashes when you try to access them via your properties(I've experienced that :)).
Every time you fetch an object you are obtaining different instances. To check if represents the same object you should compare objectID properties (to check if they are pointing to the same record in the persistent store).
If you delete one from context, you can check if the other is in context using existingObjectWithId:error
I am in a situation where I allow the user to download a PFObject and modify it locally, and they can then either cancel the changes or hit Done, which will dismiss the editing interface but NOT upload the changes to Parse yet. They need to hit Save on the previous screen to write all changes to the database at once.
The problem is once the PFObject is modified, you cannot revert it to its prior state without refetching from the database. But I cannot always refetch the data from the database every time they hit Cancel because the prior state may not be uploaded to Parse yet (and that's a bad UX making them wait to discard changes that are only stored locally).
For example, imagine the user taps to edit the PFObject, they make changes then hit Done, then tap on it again and further edit the object, then hit Cancel. In this case, the object needs to be reverted to its prior state, but that state has not been uploaded to Parse yet. So I cannot refetch the data from the database to revert changes otherwise it would overwrite the changes they made the first time.
To solve this problem, I would simply fetch the PFObject and store a copy of it. I'd call that the transient object. I would have another property that stores the real object. The user would modify the transient object, and when they hit Cancel I would simply set that to nil, if they instead hit Done I would set the real object equal to the transient object, and once they finally hit Save I would save the real object to the database. That way I can be sure changes aren't being made to the real object until the user commits the changes. The problem is, PFObject does not adopt the NSCopying protocol (not sure why), therefore I cannot create a copy of the PFObject. Any change I make to it affects the real object.
How can this be resolved, without modifying the app's design that allows control over when the data is committed and later saved? Is there a way to extend PFObject and adopt NSCopying, has it been done before?
I did consider storing the attributes of the object in a dictionary and allow the user to edit that instead, then upon commit set each of those attributes on the PFObject. The problem with this solution arises with complex structures. In this app, I allow the user to modify multiple arrays that contain multiple PFObjects. It's just infeasible to try to recreate and later merge changes with complex structures like this beyond a single simple PFObject.
I ran into this same problem. I did not make any changes directly to the PFObject, but rather, saved the updates in an NSDictionary. When the user clicks the done button, I then update the PFObject and saveInBackground. I don't think there is a "discard local changes" option for PFObject. If you don't do this, the only option is to throw out the existing PFObject and fetch again.
Regarding the NSDictionary comment, perhaps NSArray would be better. The implementation really depends on your specific program, but I'll give a quick example. The NSArray we'll call instructionArray. Imagine there are 3 sections in a tableView. Also assume that the data source for each section is an NSArray of PFObjects. Now say you want to set the age property of each PFObject in Section 2 to 35.
Add an NSArray object (corresponding to an instruction to carry out) to the instructionArray. This instruction to carry out could have the form
Section to update
Property to update
Value to update to
So the object you'll add is #[#(2),#"age",#(35)];
Given that the user is probably carrying out a finite amount of instructions, it might not be that performance heavy to loop through the instructionArray in cellForRowAtIndexPath so when a cell uses its corresponding PFObject to figure out what to display, it can loop through the instructions after and change what is displayed as if the PFObject was updated.
When the save button is touched, loop through the instructions and actually edit the PFObjects themselves.
If you need the instructions to handle specific objects rather than sections, then you just have to update the structure of the instructionArray. Maybe you could include an identifier to indicate what type of instruction it is.
I am running into an issue with CoreData (using MagicalRecord) trying to change an attribute. I think this is the result of the object having relationships to two parent entities.
The object is a manual, this has a to-many relationship to both a car and library. The library contains all manual objects. A car has 1-3 manual items.
Every manual has a UID and the same object is shared between the car and library.
For some reason, once the object is set into the relationship for both, I cannot change the title (NSString) attribute of the manual.
I checked to make sure I am in the same context. Not sure what the issue is.
This is what I am logging:
NSLog(#"Manual Title: %#",manual.title);
//prints Old Manual
manual.title = #"New Manual"
NSLog(#"Manual Title: %#",manual.title);
//prints New Manual
I'm saving this inside a MagicalRecord saveUsingCurrentThreadContextWithBlockAndWait other unrelated NSManagedObjects in the same method are being saved.
When the app loads the data into the UI, it still reads "Old Manual"
Any suggestions?
Thank you for your time.
It turns out the issue was two-fold with the MagicalRecord methods I was using:
1) Instead of saveUsingCurrentThreadContextWithBlockAndWait I should have used saveWithBlockAndWait
2) When I was fetching the manual object, I wasn't passing the context, so I changed MR_findFirstWithPredicate to MR_findFirstWithPredicate:inContext
Hopefully this will save someone else some time
I would like to separate my reference data from my user data in my Core Data model to simplify future updates of my app (and because, I plan to store the database on the cloud and there is no need to store reference data on the cloud as this is part of my application). Therefore, I've been looking for a while for a way to code a cross-store relationship using fetched properties. I have not found any example implementations of this.
I have a Core Data model using 2 configurations :
data model config 1 : UserData (entities relative to user)
data model config 2 : ReferenceData (entities relative to application itself)
I set up 2 different SQLite persistent stores for both config.
UserData config (and store) contains entity "User"
ReferenceData config (and store) contains entities "Type" and "Item".
I would like to create two single-way weak relationships as below :
A "User" has a unique "Type"
A "User" has many "Items"
Here are my questions :
How do I set up my properties?
Do I need 2 properties for each relation (one for storing Unique ID and another to access my fetched results)?
Could this weak relationship be ordered?
Could someone give me an example implementation of this?
As a follow-on to Marcus' answer:
Looking through the forums and docs, I read that I should use the URI Representation of my entity instance instead of objectID. What is the reason behind this?
// Get the URI of my object to reference
NSURL * uriObjectB [[myObjectB objectID] URIRepresentation];
Next, I wonder, how do I store my object B URI (NSURL) in my parent object A as a weak relationship? What attribute type should I use? How do I convert this? I heard about archive... ?
Then, later I should retrieve the managed object the same way (by unconvert/unarchive the URIRepresentation) and get Object from URI
// Get the Object ID from the URI
NSManagedObjectID* idObjectB = [storeCoordinator managedObjectIDForURIRepresentation:[[myManagedObject objectID] URIRepresentation]];
// Get the Managed Object for the idOjectB ...
And last but not least, shouId I declare two properties in my entity A, one for persisting of URI needs and another for retrieving direclty object B?
NSURL * uriObjectB [objectA uriObjectB];
ObjectB * myObjectB = [objectA objectB];
As you can read, I really miss some simple example to implement thes weak relationships ! I would really appreciate some help.
Splitting the data is the right answer by far. Reference data should not be synced with the cloud, especially since iCloud has soft caps on what it will allow an application to sync and store in documents.
To create soft references across to stores (they do not need to be SQLite but it is a good idea for general app performance) you will need to have some kind of unique key that can be referenced from the other side; a good old fashioned foreign key.
From there you can create a fetched property in the model to reference the entity.
While this relationship cannot be ordered directly you can create order via a sort index or if it has a logical sort then you can sort it once you retrieve the data (I use convenience methods for this that return a sorted array instead of a set).
I can build up an example but you really are on the right track. The only fun part is migration. When you detect a migration situation you will need to migrate each store independently before you build up your core data stack. It sounds tricky but it really is not that hard to accomplish.
Example
Imagine you have a UserBar entity in the user store and a RefBar entity in the reference store. The RefBar will then have a fetchedProperty "relationship" with a UserBar thereby creating a ToOne relationship.
UserBar
----------
refBarID : NSInteger
RefBar
--------
identifier : NSInteger
You can then create a fetched property on the RefBar entity in the modeler with a predicate of:
$FETCHED_PROPERTY.refBarID == identifier
Lets name that predicate "userBarFetched"
Now that will return an array so we want to add a convenience method to the RefBar
#class UserBar;
#interface RefBar : NSManagedObject
- (UserBar*)userBar;
#end
#implementation RefBar
- (UserBar*)userBar
{
NSArray *fetched = [self valueForKey:#"userBarFetched"];
return [fetched lastObject];
}
#end
To create a ToMany is the same except your convenience method would return an array and you would sort the array before returning it.
As Heath Borders mentioned, it is possible to add a sort to the NSFetchedProperty if you want but you must do it in code. Personally I have always found it wasteful and don't use that feature. It might be more useful if I could set the sort in the modeler.
Using the ObjectID
I do not recommend using the ObjectID or the URIRepresentation. The ObjectID (and therefore the URIRepresentation of that ObjectID) can and will change. Whenever you migrate a database that value will change. You are far better off creating a non-changing GUID.
The weak relationship
You only need a single value on the M side of the relationship and that stores the foreign identifier. In your object subclass you only need to implement accessors that retrieve the object (or objects).
I would go with just one store.
For storing stuff in the cloud, you will anyway have to serialize the data, either as JSON or SQL statements, or whatever scheme you prefer.
You will need a local copy of the data on the user's device, so he can access it quickly and offline. The cloud store can have only the user entity, while the local store (part of the app) can also have the reference entity.
I have a similar project with a huge reference store (20000 records) with geographic information, and user generated content ("posts"). I use a single store. When I ship the app, the "posts" entity is also defined but empty. When I update the data model I simply re-generate the whole reference store before shipping.
I see absolutely no reason to go for a cross store solution here.