How to properly use createOrUpdateInRealm? - ios

I am using Realm to handle persisting data to disk in my app. I am storing an ArticleCollection object to Realm (this just contains an array of NewsArticle objects).
I am using createOrUpdateInDefaultRealmWithValue to either create a new ArticleCollection or update the existing one (achieved using a primary key on ArticleCollection).
This currently works well when the app fetches new articles it will overwrite the current collection of articles and replace with a new instance of ArticleCollection.
The issue is that whilst ArticleCollection is replaced (in Realm brwoser I can see the count of ArticleCollection is always 1 as expected), the number of NewsArticles always increases. It seems that sub objects of ArticleCollection (NewsArticle) never get replaced/deleted, only appended to.
How can I ensure that when I use createOrUpdateInRealm, it will delete all sub objects?

Make sure that are also using a primary key on your NewsArticle object. Otherwise those news articles won't be updated. That's why you are seeing an increasing number of news articles.

Related

Why does storing a reference to an NSManagedObject prevent it from updating?

This question is poorly phased but this can be better explained in code.
We have a Core Data Stack with private and main contexts as defined by Marcus Zarra here: http://martiancraft.com/blog/2015/03/core-data-stack/
We call a separate class to do a fetch request (main context) and return an array of NSManagedObjects:
NSArray *ourManagedObjects = [[Client sharedClient].coreDataManager fetchArrayForClass:[OurObject class] sortKey:#"name" ascending:YES];
We then do some processing and store a reference:
self.ourObjects = processedManagedObjects
Our view contains a UITableView and this data is used to populate it and that works just fine.
We change the data on our CMS, pull to refresh on the UITableView to trigger a sync (private context) and then call this same function to retrieve the updated data. However, the fetch request returns the exact same data as before even though when I check the sqlite db directly it contains the new data. To get the new values to display I have to reload the app.
I have discovered that if I don't assign the processedManagedObjects to self, the fetch request does indeed return the correct data, so it looks like holding a reference to the NSManagedObject stops it from getting new data from the main context. However I have no idea why that would be.
To clarify, we're pretty sure there's nothing wrong with our Core Data Stack, even when these managed objects are not being updated, other are being updated just fine, it's only this one where we store a local reference.
It sounds like what's going on is:
Managed objects don't automatically update themselves to reflect the latest data in the persistent store when changes are made via a different managed object context.
As a result, if you keep a reference to the objects, they keep whatever data they already had.
On the other hand if you don't keep a reference but instead re-fetch them, you get the new data because there was no managed object hanging around with its old data.
You have a few options:
You could keep the reference and have your context refresh the managed objects, using either the refresh(_, mergeChanges:) method or refreshAllObjects().
If it makes sense for your app, use an NSFetchedResultsController and use its delegate methods to be notified of changes.
Don't keep the reference.
The first is probably best-- refreshAllObjects() is probably what you want. Other options might be better based on other details of your app.
Try setting the shouldRefreshRefetchedObjects property of the fetch request to true. According to the documentation:
By default when you fetch objects, they maintain their current property values, even if the values in the persistent store have changed. Invoking this method with the parameter true means that when the fetch is executed, the property values of fetched objects are updated with the current values in the persistent store.

When I try to save data to Firebase it only updates the existing data, Swift, Firebase

The way I set up my database structure was like this:
It starts with Lists then there is a child that shows the users UID then inside that there is one item.
The one item inside the UID gets updated every time I attempt to save new data. Instead of adding another item the same one just keeps changing. I was wondering how I could instead of update the same one item every time add more items.
The way that I save my data is with this line of code.
let user = FIRAuth.auth()?.currentUser
let item: String = self.ItemTextField.text!
self.ref.child("Lists").child(user!.uid).setValue(["Items": item])
More idiomatic is to store the list of items with so-called push ids:
Lists
twEymn...
-Km....: "Yoghurt"
You'd do this with:
self.ref.child("Lists").child(user!.uid).childByAutoId().setValue(item)
The childByAutoId() generates a unique, sequential ID. It's similar to an array index, but this one works reliably in multi-user environments and when users can be offline. Read this blog post about these so-called push ids.
Alternatively you can use the name of the item as the key (if the item has to be unique in the list):
Lists
twEymn...
"Yoghurt": true
In that case the code becomes:
self.ref.child("Lists").child(user!.uid).child(item).setValue(true)
One thing you'll note is that both of these approaches only deal with the newly added item, instead of the list of items as a whole. This is a general pattern you'll see when using Firebase. By isolating your modifications, your app will be more scalable without users getting into each other's way.
The problem is that you are setting a key-value pair ("Items" : item) so that each time it is updating the value for the same key. What you could do instead is ("Items" : [your array of items here]), which will update a list of items for the same key each time.
You could also fetch the current list of items, append your new item locally, and then update.

Fix uneccessary copy of NSManagedObject

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.

Parse.com - Find deleted objects

I have an iOS app that synchronises to Parse.com.
It can find anything that was added to Parse and add it to Core Data using PFQuery. It can also check for any data that has been updated and update accordingly.
However, I'm not sure how to find objects that have been deleted on Parse.com.
Does anyone know of a query that will list the ObjectIDs that have been deleted and the date of their deletion? I can then remove them from the Core Data on the app.
I needed this function, too, but figured that marking rows as deleted will bloat the data and add a condition to every query. So I created a Deletion class. It records only the class name and ID of any deleted row, so it stays pretty small:
function recordDeletion(klass, identifier) {
var Deletion = Parse.Object.extend("Deletion");
var deletion = new Deletion();
deletion.set("klass", klass);
deletion.set("identifier", identifier);
return deletion.save();
}
// for every class that you want deletions recorded, add one of these...
Parse.Cloud.beforeDelete("MyClass", function(request, response) {
recordDeletion("MyClass", request.object.id).then(function() {response.success();});
});
My iOS clients record the date when they last fetched data, then get everything newly created/updated from MyClass (+ others) and Deletion. With that, the can delete the Deletions locally.
Over a longer period, the clients remove all of the locally cached data and get a fresh copy of everything (except Deletions). This allows me to have a scheduled job on the server that will empty the Deletion table (on a cycle that's much longer than the client's cycle).
There is no provided API for this.
As per the comment from #Fogmeister you can tag objects as deleted and update like that. Alternatively you can maintain a specific list of deleted ids (potentially using Parse.Cloud.beforeDelete) and then make a specific request to get only the deletions.
In either case you will need to explicitly manage the scheme you choose and also decide how and when to clean up the deleted objects / deletion records.

Cross-Store weak relationship with Fetched Properties?

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.

Resources