swift / CoreData - Create "dummy" NSManagedObjecIDs for data models (to test things without the need of managed objects) - ios

Say I have 2 NSManagedObjects in CoreData.
class House: NSManagedObject {}
class Location: NSManagedObject {}
I also have data model structs like this:
struct HouseModel {
var objectID: NSManagedObjectID
...
}
sruct LocationModel {
var objectID: NSManagedObjectID
...
}
For each loaded managedObject I basically use its attributes to initialize a new model struct to use for the UI and stuff (mainly collection views)
I have to have the NSManagedObjectID attribute in the structs in order to be able to make changes to the managedObject that struct belongs to. (I learned that I should use the mainViewContext only for reading while using something like persistentContainer.performBackgroundTask for writing. Thus, I need the NSManagedObjectID to load the objects on a background queue)
That's working but there is a problem with this approach:
I can't initialize one of these data models without a managed object. That's annoying when I want to create dummy data for UI testing or unit testing.
I know one solution: Create a Dummy managedObject with exactly one instance and use its objectID for stuff like that. But I don't really like this. Is there a better / more convenient way?
I mean, I would love to entirely remove the objectID attribute to keep CoreData separate from these model structs. But I don't see a way to do this. I need the connection.

For passing NSManagedObjects to a detail view for editing, it is often useful to do that on a new main queue managed object context, which simplifies your UI access and allows you to throw away the context if the user cancels changes.
But that's not what you asked.
Your problem is that you want to identify a managed object, but not use NSManagedObjectID. For this, you can use a URL property instead. NSManagedObjectID has a uriRepresentation() that returns a URL, and NSPersistentStoreCoordinator can convert a URL back into a managed object ID using managedObjectID(forURIRepresentation:). So you can store any old URL in the struct for testing purposes, and still be securely referring to managed objects in your app logic.

Related

Continue using NSManagedObject types but migrate off Core Data

I am working with a massive, existing code base that uses Core Data.
I have types that extend NSManagedObject and are persisted in Core Data.
I need to migrate off of Core Data entirely (the reasons for this don't matter, but I have good reasons). However, I have a fundamental constraint.
I cannot change these types to NSObject (some use cases must continue using Core Data).
These NSManagedObject types are heavily passed around my business logic. I don't want to refactor that business logic and introduced a new/"unmanaged" type.
Let's say I have some type Foo that's an NSManagedObject. I tried something like:
Foo *foo = [[Foo alloc] init];
foo.name = "beebunny";
The foo.name call causes a crash.
name is #dynamic and has a custom set method, something like:
- (void)setFoo:(Foo *)fooIn
{
[self setFoo:fooIn];
}
The [self setFoo:fooIn]; call causes an exception (unknown selector).
It seems like I have to use Core Data if I'm working with any type that extends NSManagedObject.
Is there a proper/recommended pattern for the type of migration I'm wanting to perform off of Core Data?
Instances of NSManagedObject depend very heavily on the data model. You don't have to save the instances with Core Data, but they must have a data model backing them up or they won't work. Your [[Foo alloc] init] doesn't work because (a) it doesn't use a designated initializer, and (b) it doesn't have a data model supporting it.
You can create instances that you don't save. For example you can use -initWithEntity:insertIntoManagedObjectContext: to create an instance, but have the context argument be NULL. It'll never be saved unless you insert it, but it sounds like you won't do that. But that initializer requires an NSEntityDescription, and you need to get that from a managed object model. (You can also create them in code, but that's not going to make it easier or remove the need to import Core Data into your code).
In short, you don't have to save these objects to Core Data, but you do need to have some Core Data support if you'll still be subclassing NSManagedObject. You can't use those classes independently of the data model.

Want to save Core Data but "Extensions must not contain stored properties"

I have a Core Data class called "CloudPage", and I've added an extension for it in my code:
extension CloudPage:GalleryItem {
// bunch of methods defined here
func someFunc() -> Bool {
return true
}
It made sense to me to extend this class to have all model information reside here. I also thought it made sense for this class to know how to save an instance of itself, so I added my viewcontext to it:
extension CloudPage:GalleryItem {
#Environment(\.managedObjectContext) private var viewContext
// ...some code...
But now swift says "Extensions must not contain stored properties". Is there a better way to do this? I would think all saving logic should reside on this model.
As the compiler already say, it is not allow to add stored properties in Extension. CloudPage is an NSManagedObject which means it is already managed by CoreData.
Anyway this is not how you work with CoreData Objects. I'll clarify some stuff
These objects are managed/loaded in a context. There are 2 types of contexts
ViewContext (should be used for view purposes)
BackgroundContext (should be used for loading stuff from e.g. APIs in the background)
If you update objects or add new ones, these already live in said context. To persist these objects you don't have to "push" them in there anymore like you'd do it with plain SQL (UPDATE table...). You simply have to save the context like
// context is the one you loaded the objects from and worked with
context.save()
I hope this helps, i just wanted to sum it up roughly for you to get a better understanding. Don't become desperate, CoreData has its quirks and you'll get used to it. For more details read Apples Documentation of working with Core Data.

Multiple Contexts in iOS Core Data

I have a few questions around the creation of managed object contexts in core data in my app if you can help out please...
To simplify, say my app an entity Street and another entity House. Each Street object has various attributes, including an attribute houseList (NSArray) (which is of Transformable type) of House objects. If I do not introduce the House entity and have Core Data only for Street, everything works fine and I'm able to save the context, load all House objects in a given street, etc.
But the moment I create an entity for House (I am saving it in the same MOC as Street) and run setHouseList, the next time I launch the app, I get the usual error "CoreData: error: Failed to call designated initializer on NSManagedObject class 'House'". Following questions that I have around this...
Does this situation also mean that I have different threads at play? Apologies for the ignorance but per my understanding, there is no background thread here doing a parallel update, so ideally these are not separate threads, thus I should not be requiring a separate managed object context.
I even tried declaring a new MOC property in the app delegate and passed that through to the view controller where setHouseList is called, and then also saved any House objects in this new MOC. That hasnt helped either and I get the same error.
I'm suspecting I might have to use ObjectID whilst calling setHouseList if I use a new MOC, but somehow cant get my head around how to do that... I've further gone through the https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Concurrency.html and https://www.cocoanetics.com/2012/07/multi-context-coredata/ links but not making any progress... any inputs would be much appreciated!
Thanks!
You should really set up a to-many relationship between House and Street entities, where one Street can have many Housees associated with it.
If order is important (which I would imagine it wouldn't be), then you can model the relationship as an NSOrderedSet, though NSSet sounds like it would be just fine in this case.
class Street: NSManagedObject {
#NSManaged var houses: NSOrderedSet
}
class House: NSManagedObject {
#NSManaged var street: Street
}
Then, when you're creating the objects, set the street property on the House and add the House to the set of houses on the Street.
func addHouse(house: House) {
let houses = self.mutableSetValueForKey("houses")
houses.addObject(house)
}
Core data will handle there rest from there.

How to use Core Data model subclasses outside a Core Data context?

I'm trying to make a weather app in Swift that will save the cities I add to Core Data, each city contain a weather object that is also saved to Core Data and other various variables.
But I soon figured out, using Core Data NSManagedObjects subclasses outside a Core Data context is close to impossible (dealing with NSNumber and similar, no custom init, forced to save them somewhere, what if I stop using Core Data tomorrow, ...).
So what's the best practice to keep using Core Data but also use models outside of its context?
My solution right now is to create a Class for each Model, so :
class City
{
var country: String?
var name: String?
// ...
}
Is the corresponding class of :
class CD_City
{
#NSManaged var country: String?
#NSManaged var name: String?
// ...
}
So I can use City anywhere and anyhow I want. But I need a func to turn a City into CD_City and opposite. So I'm really not sure I'm doing it the best way.
Also what would you recommend as a conversion method ?
(FYI I'm using MagicalRecord as a Core Data helper)
TL;DR - Don't do that or things will break.
There used to be various hacks for getting it to sort of work, but they all rely on undocumented behavior in CoreData. I would never use anything like that in code I wanted to show another human being, much less ship to customers. CoreData needs to insert proxy objects that hook into property change events on your model objects, and the only way it can reliably do that and track the original data values were is if it is responsible for creating those entities in the first place; That also makes the faulting & uniquing system work. Don't think of Core Data as an ORM, it really is an object graph management framework, and as such it is designed to be used a certain way with no easy solution to side step it safely.
If you don't want to save an NSManagedObject or a subclass of it, then you can create it with
init(entity entity: NSEntityDescription, insertIntoManagedObjectContext context: NSManagedObjectContext?)
and pass nil for insertIntoManagedObjectContext this will create you an instance but it will be not be saved to the MOC.
In case you have to save it to the MOC later, you can use NSMangedObjectContext's
func insertObject(_ object: NSManagedObject)

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