Xcode 8. Core Data in 5 Easy Steps
Xcode 8 has brought some changes to how we set up a Core Data model. This post will run though a very simple example of setting up a Core Data model, entities, relationships, and accessors. I will assume you already have the CoreDate framework added to your project and that you have an active instance of a ManagedObjectContext to recall and save objects.
If you're here searching for an answer to the "Linker command failed with exit code 1" error with a report of duplicate symbols, go straight to Step 5 below. I've explained one possible fix down there.
1. Find / Create a Data Model file
2. Create the entities
3. Add entity attributes
4. Add entity relationships
5. Let Xcode generate your NSManagedObject subclassed files that will allow you easy peasy access to your properties and relationships.
In the end, we'll have something like this:
Find / Create your Data Model
If you clicked "Use Core Data" when you first created your project, you'll see it as the .xdatamodeld file.
Oops! I don't have Core Data enabled...
No problem. If you don't have Core Data in your project, it's an easy fix. First add the framework in your project settings.
Then go to File > New > File and add a Data Model file to your project.
SUPER IMPORTANT (If you are coding Objective C)!
If you add your own file as shown above, Xcode will have it set to Swift. So unless you want to be scratching your head for an hour wondering why you are getting Swift categories in your app like some developers do (and by 'some' I mean 'me'), then select this new file, go to the identity inspector, scroll way down and set your code base to Objective C. If you're in Swift, then just go make a coffee and join us below.
And...of course... @import CoreData into the file where you will be creating your ManagedObjectContext.
Create your entities
As you know, an entity typically represents a data object in your app. Let's consider an event app. An event app might need:
1. An Event Object, with properties such as the event name and date
2. A Person Object, with properties such as name, gender, or interests
3. A Message Object, with properties such as sender, text, image, date sent
A Settings entity is also useful, which you can use to store server tokens or other state information.
Creating entities can be done in both views, the list view and the grid view. In either case, just click + to add an entity. Then select the new entity and give it a name.
You will also need a name for the managed object class that will be generated. In the Data Model Inspector (on the far right), enter the entity name with MO appended (not required, but it's convention to make your MO (Managed Object) classes explicit).
Attributes correspond to properties, and they include Strings, Data, Dates, etc. One less well-known type is "Transformable." We can use this to correspond to pretty much any object that conforms to the NSCopying or NSSecureCoding protocols. In our event example, I have a CLLocation object as well as an array (later on) of a Person's favorite event types (an array of NSStrings). More on this later.
To add an attribute, click + either in the attributes list or at the bottom of the screen. Give the attribute a name (first letter must be lower case) and set the object type.
When setting your attributes, if you are using a BOOL or a float or some other scalar value, you can choose to use it as such. This will allow you to set the value directly in your app. Or, if you would like to wrap everything in an object before setting properties, leave the Use Scalar Type option unchecked. Also, with Scalar values in particular, set a default value to make life easier on your program's database query and management processes.
For attributes that contain large blobs of data such as sounds, images, or video, you have a choice. The difficult choice is to manually save data over 1MB to disc and just store the file URL in Core Data. The easy way is to let Core Data manage that for you but simply checking the "Allows External Storage" option in the identity inspector. The easy way sounds good to me!
Here is what the Data Model looks like with some more attributes added, viewed in Grid View.
Looks nice and clean, right!
Relationships are a bit more challenging, but in this app, relatively simple. Here are the relationships we'll use in this example.
Event => Person (attendee) is a to-many relationship. That is, one event can have many people (Attendees).
Event => Person(event host) we'll set this as a to-one relationship. Each event has one host.
Person => Event (myHostedEvents) is to-many. A person can host multiple events, depending on their budget.
Person => Event (myJoinedEvents) is to-many. A person can attend multiple events, depending on their alcohol tolerance.
Event => Message (messages) is to-many. An event will have many messages, which can be passed to a chat window manager along with the participants.
Message => Event (eventMessageRoom) is to-one. A message belongs to only one message room.
Adding a relationship is a 3-step process.
1. Select an entity.
Click + to add a relationship. Then give the relationship a name (a description of the relationship as viewed from that object). So from the Event to the Person who is hosting the event, we can say, "host."
The destination of this new "host" relationship is the Person object. We don't name the person a "host"...all of this information is held in the name of the relationship. At this point, don't worry about "inverse."
2. Set the inverse relationship.
The inverse is the opposite of the host relationship. Select the Person entity, click + and add a "myHostedEvents" relationship. The destination of this relationship is the Event.
3. Set the relationship inverse, type, and delete rule.
Now that you have a 2-way relationship defined, set the inverse, and in the Relationship Inspector, specify if the relationship as either "To One" or "To Many."
Above, I have set the inverse relationship of "myHostedEvents" to "host." Furthermore, as a person and social butterfly, I can have many events, so I set this as a "To Many" relationship.
Below, I am looking at the relationship from the perspective of the Event. The host relationship is set as "To One" because, as we have said, an event will have only one host.
Finally, after you are really, really, ridiculously sure you know how you want your objects to interact, set your delete rules. The most dangerous type of rule is "cascade." This means if Object A is deleted, then any objects on the inverse side of that relationship are also deleted. For example, I have set the relationship of the Person (Host) --> MyHostedEvents to cascade.
What doe this mean? Well, in this app, if the host decides to delete his or her account, then any events that were created by the host will also be deleted.
In an actual app, I probably wouldn't delete events because I might want to keep a record of the events the user held in the past. But if you need to delete objects down the food chain, cascade is how you do it.
Done like dinner!
And here is what the object graph looks like with the relationships added in graph view. When you do this in your own app, if you aren't seeing an inverse, check the destination. It's VERY easy to get the directions mixed up. Clear naming of relationships helps. As does a decent espresso machine.
In practice, you may want to use a "join object" to manage your "many-to-many" relationships. What is a many-to-many relationship? A good example is the event <==> attendee relationship. An event can have many attendees. Inversely, a person can attend many events. That's many-to-many. A join object (similar concept to using a join table in a relational database) is an intermediate object to allow you to represent to-many relationships with a bit more finesse.
What do I mean by finesse? Well, in our current model, we can indeed connect multiple people (attendees) to multiple events and vice versa. But that's a yes-no (joined/not joined) relationship. Kinda boring. What if we want to add a bit more pizzazz to that relationship? For example, what if we want to introduce a system where people must apply to the event, and you as the host must approve all guests? In this case, we could create an "Attendee" Entity. It would allow us to define various states of being an attendee -- if they have requested, been allowed to join, been denied, or are on the VIP list (Yes, please!!). This is where a join entity / object really helps.
Can you see how this also simplifies things? Each Attendee entity can only have one event and one person (attendee). So in addition to being able to mess with things like attendee status without complicating your Person objects, you can do a fairly simple query to Core Data to find all attendee objects for an event...or all events that one person is attending. How cool is that!
Generate your managed object subclasses.
(Linker Error issue described in this section)
This is where Xcode starts doing some heavy lifting. Before you do this step, go to Product > Clean, or holding the alt (option) key, Product > Clean Build Folder to clean out any intermediates. If you are constantly trashing your old managed object files and regenerating new ones, cleaning will prevent some issues of Xcode suddenly not reading your import files correctly.
Here we go! Just select any or all of your entity objects, then in the Editor menu...
Editor > Create NSManagedObject Subclasses > select your data model > next > select the entities for which you need files generated > Next > Save
Your ManagedObject files will now appear in your project folder
Houston, we have an problem...
If you build now, you'll get a linker command failed with exit code 1 (use -v to see invocation) error because you have duplicate symbols (duplicate file names). Although Xcode kindly gives you all the CoreDataClass files and the files that contain the properties (attributes and relationships), having both of these files causes a redundancy in the form of a file duplication in your build folder. In fact, it will look just like this:
How to fix it:
1. Trash all of these files from your project folder.
2. Build the app.
3. Import the files into any class where you'll be using one of your Managed Objects in the form of:
For example: #import "EventMO+CoreDataClass.h"
I still can't get the thing to build:
If you have to keep these files in your project then here's a workaround
1. Go to your project window and in your app's Target > Build Phases > Compile Sources
2. Trash the CoreDataClass.m files.
That will leave just the CoreDataProperties.
**NOTE: If you change or add attributes / relationships after building, you will need to build again. This should be enough for your app to recognize the new properties. If it isn't, then do a Product > Clean Build Folder, select your entities, then Editor > Generate NSManagedObjects. At this point, provided you did not add any new entities, you should be able to just trash your EventMO+CoreDataClass.h etc. files and keep on working.
And that's it. With this you should be able to create new entity descriptions, pass around managed objects, save them and do cool stuff!
OK, just a bit of code here. If you are saving arrays or more complex objects to Core Data as Transformable attributes, you'll need two tricks.
1. For most objects (CLLocations, server tokens, etc.)
When you retrieve the property from Core Data, just cast the property as the class you expect it to be:
2. For more complex stuff, learn to love NSValueTransformer
With a simple array of strings, the trick in 1) should work. But if you're saving CKReference objects or something a bit more complicated, you might get your arrays back empty. So just run your object though an NSValueTransformer before saving them:
If you're doing a lot of this, you might want to consider making a custom NSValueTransformer class and specify the transformers you want to use in the attributes inspector of your attribute in Core Data. NSHipster has a great explanation of how to use NSValueTransformer (definitely one of my favorite go-to sites! Love NSHipster!).
Or back again when recovering them from Core Data. Other than that, most things should work as expected.