IntroductionWelcome| 00:03 | Hi, this is Simon Allardice, and
welcome to Core Data for iOS and OS X.
| | 00:08 | In this course I'll begin by talking
about what Core Data is and how it can help us.
| | 00:13 | We'll explore the vocabulary and the ideas
of Core Data, terms like managed objects,
| | 00:17 | predicates, and what these mean.
But we'll quickly get hands-on.
| | 00:21 | Let's start by modeling our data using the
tools in Xcode to describe our applications
| | 00:26 | in a way that Core Data understands and enables
us to easily save or persist that information.
| | 00:32 | If we've saved it, we need
to be able to retrieve it.
| | 00:34 | We'll see how to fetch, bring our data
back, and tie it into a user interface.
| | 00:39 | Beyond the most trivial of applications, Core
Data is what you should be using to work with
| | 00:44 | data in your iOS and Mac applications.
| | 00:47 | It's an essential competency for an Apple developer.
Let's get started.
| | Collapse this transcript |
| What you need to know| 00:00 | This isn't a beginner class.
Core Data is hard.
| | 00:04 | It is worth it, but it's hard, and
it places some expectations on you.
| | 00:07 | So do this course once you know your way around
Xcode and Objective-C, and when you're comfortable
| | 00:12 | with making a least basic iOS
or basic Cocoa applications.
| | 00:16 | Now, Apple themselves will tell you that
Core Data is not an entry-level technology, and
| | 00:21 | it assumes knowledge of regular
Cocoa or iOS development skills.
| | 00:25 | And we're going to need those skills,
things like delegation, target/action, key/value
| | 00:30 | coding, MVC, inheritance, notifications.
I expect you to know these thing.
| | 00:35 | Although, if you feel like you're an
expert on everything, that's okay as long as you
| | 00:39 | can look things up when you need to,
| | 00:42 | although I don't expect you to
know anything about Core Data itself.
| | 00:45 | Now, you might have attempted to
learn about Core Data already,
| | 00:48 | but I'm going to start here as if you hadn't.
| | 00:51 | I'll begin with the idea that you've heard
this term Core Data, you keep hearing it,
| | 00:55 | you can tell it's something
important, but you don't know much about it.
| | 00:58 | You just know it's something to do
with saving data in your application.
| | 01:01 | Now as we'll see, it's a lot more than
that, but that's where I am going to begin.
| | 01:06 | Finally, because we're working with data,
it is useful although not essential to have
| | 01:11 | experience with relational databases.
| | 01:13 | That could be Oracle or SQL Server or
something simpler like Access or FileMaker.
| | 01:18 | Now, make no mistake, this is not the same
thing. Core Data is not a database, though
| | 01:24 | we will talk about exactly
what it is in just a moment.
| | 01:27 | But if you have that background of dealing with
hundreds or thousands of pieces of interrelated
| | 01:32 | data, then you've experienced some of the
same issues, ordering it, filtering it, saving,
| | 01:37 | relationships, and dependencies, problems
that come with the territory of working with
| | 01:42 | data, whether large or small amounts of it.
| | Collapse this transcript |
| Using the exercise files| 00:00 | If you're a Premium Member of lynda.com, or if
you're watching this tutorial on a DVD, then
| | 00:05 | you'll have access to
exercise files for this title.
| | 00:08 | I've expanded them here onto my desktop, and
inside this folder, each of the relevant chapters
| | 00:13 | will have its own folder, and there are
some projects that I'll be working with during
| | 00:17 | the course to provide some simple
starter code to save us some time.
| | 00:21 | We'll point out any files at the relevant time
just by showing you the path to the file location.
| | 00:26 | There are also some finished examples, if you want to
compare and contrast what you've done with what I've done.
| | 00:32 | But they are all just for convenience.
| | 00:34 | If you don't have access to the exercise files,
you can follow along from scratch or with your own assets.
| | Collapse this transcript |
|
|
1. Getting StartedLearning Core Data| 00:00 | More than any other area of iOS and Cocoa,
I'll talk to developers who seem to just hit a
| | 00:05 | brick wall getting started with Core Data and
find it difficult to get a handle on this technology.
| | 00:10 | Now let me talk about why for just a moment,
because it is easy to approach Core Data the
| | 00:14 | wrong way and end up wasting your time.
| | 00:17 | The first problem is actually with the first question
most developers ask, what exactly is Core Data?
| | 00:23 | Because I can answer that, but I end up
using phrases that don't really help.
| | 00:28 | I can quote Apple that Core Data is quote a schema-driven
object graph management and persistence framework.
| | 00:35 | This is technically true, but it's not helpful.
| | 00:37 | It doesn't give us any sense of what we
need to do and how we would deal with it.
| | 00:42 | The next problem is that most developers
will jump into asking questions to try and force
| | 00:46 | an equivalent to something they already know,
and I'll always have to answer these with
| | 00:50 | a phrase that begins, "No, but..."
| | 00:52 | So is Core Data a database?
No, but it might use one.
| | 00:57 | If they have a Java background, they might ask
is Core Data like Hibernate? Again, no, but.
| | 01:03 | Or if they come from .NET, they might ask is
it like entity framework, or is it like some
| | 01:08 | other technology you might
know on a different platform?
| | 01:11 | And once again, no, but there might be some
comparisons sure, because a lot of things
| | 01:17 | deal with the same problem space.
| | 01:20 | However, this whole approach is doomed from
the beginning, and I suggest you avoid it,
| | 01:24 | and don't obsess about trying to make Core Data
exactly like something you already know. It isn't.
| | 01:29 | Now if you have ever started to read a tutorial
on Core Data, you'll find that most will quickly
| | 01:35 | hit you with architectural diagrams.
| | 01:38 | What's often referred to as the Core Data
stack, the technical pieces of this framework,
| | 01:43 | all these new objects we now have access
to and how they interact with each other.
| | 01:48 | And you will see this kind of thing a lot
in more or less detail, but this like most
| | 01:52 | architectural diagrams is once again technically true
but only useful to people who know most of it already.
| | 01:59 | Whenever you see something like this,
you're seeing the result of someone who's worked
| | 02:02 | their way through a technology and is
writing about it from the other side.
| | 02:07 | So for them, this diagram is a good compact
reminder. It's useful and contextual because
| | 02:12 | they already know which of these things are
the most important, which take the most work,
| | 02:16 | what order they are done in a project, which
classes they need to care about, and which they don't.
| | 02:21 | And this diagram tells us none of that.
So we are not going to do it this way.
| | 02:25 | We are going to take a different
approach to learning Core Data.
| | 02:28 | We will see this popular Core Data stack
diagram, but we'll see it later on when it helps.
| | 02:34 | However, let me bring it back for a second,
because this diagram does have one thing right,
| | 02:39 | and there is a reason I show it to you now.
| | 02:41 | Now don't worry about what all
these names represent right now.
| | 02:45 | Just understand that Core Data is not one piece.
| | 02:49 | It's not one really important class, nor
like many other frameworks in Apple development
| | 02:54 | is it a bunch of vaguely-related utility
classes you just pick and choose from as you see fit.
| | 03:00 | Instead, Core Data is several large pieces
of functionality designed to work together.
| | 03:06 | It is a machine full of moving parts,
| | 03:09 | objects deeply dependent on other objects
that's being handed to you by Apple.
| | 03:13 | And that's what can make
it difficult to approach.
| | 03:16 | If we can pull this apart and take it piece
by independent piece, it would be much easier,
| | 03:21 | but there are no single parts of Core Data
that makes sense all by themselves.
| | 03:25 | It's all or nothing.
| | 03:27 | You try and focus just on one piece, and
you'll find it drags all the others along with it,
| | 03:32 | and that makes things a little
different when we are learning it.
| | 03:35 | Think about it this way.
| | 03:36 | When you first learn regular iOS or Cocoa
development, or indeed pretty much any programming
| | 03:41 | language, there are ways to
smooth the learning curve.
| | 03:45 | You can begin by creating
a Hello World application.
| | 03:48 | Then you can make a simple
app that works with integers.
| | 03:50 | You can make one with strings.
Then you learn how to work with dates.
| | 03:54 | You make an app with one button.
| | 03:56 | You can make an app with
a button and a slider.
| | 03:58 | You can slowly learn new objects one by one
and grow your knowledge bit by bit, and that
| | 04:04 | kind of learning curve doesn't really stop.
| | 04:07 | You can go on like this for years, but
learning Core Data on the other hand, that's kind of like
| | 04:12 | learning to ride a bicycle.
| | 04:14 | You have to do it all at once,
and you have to do it all up front.
| | 04:18 | You don't learn to ride a
bicycle by taking it apart.
| | 04:21 | You don't spend a day first just spinning
the right pedals and then a day spinning the
| | 04:25 | left pedals and then a
day moving the handlebars.
| | 04:27 | You have to do it all together, balancing,
steering, pedals, brakes. At some point you
| | 04:32 | will have to get on and do
all of it at the same time.
| | 04:35 | Because up until the point where you can do
all of these things together, there is actually
| | 04:40 | no benefit to just knowing one part of it.
That's the same with Core Data.
| | 04:45 | There's no benefit to
knowing just one part of this.
| | 04:47 | You need all of it working together.
| | 04:50 | What that means is that Core Data is a steep
learning curve, although what's tough to see
| | 04:55 | from here is it's a short learning curve.
| | 04:58 | You must have all the pieces in place to
make Core Data work at all, but as soon as it is
| | 05:02 | in place you're done. You're rolling.
It's all downhill from there.
| | 05:06 | We just need to get over that first hump.
| | 05:09 | There are several things to learn, and we will
need to get them all moving at the same time.
| | 05:14 | So what are those things?
How do we start?
| | 05:16 | Well, the best way to begin is
actually by asking a different first question.
| | 05:22 | Not what, what exactly is
Core Data technically, but first, why?
| | 05:27 | Why does this exist, why was Core Data
invented, and what problems is it a solution for?
| | Collapse this transcript |
| Why use Core Data?| 00:00 | So here's the world we're living in.
Let's say I've written an application.
| | 00:04 | It could be on the iPhone,
the iPad, the desktop.
| | 00:07 | That really doesn't matter.
| | 00:08 | I use this app, and as I do my
code is instantiating objects,
| | 00:13 | some of these are simple,
NSString, NSSDate, NSArrays.
| | 00:16 | More importantly, I have my own custom objects from
classes I've written myself for this application.
| | 00:23 | My model objects the M part of MVC.
| | 00:27 | Now I might have objects that themselves
contain other objects or have a reference from one
| | 00:31 | to another, and I end up with perhaps a handful of
these, but perhaps hundreds or thousands of objects.
| | 00:36 | Now this is what's referred to as an
object graph, meaning at a particular instant in
| | 00:41 | time the state of all of the objects in
my application and how they are related.
| | 00:46 | But now I need to exit this application.
| | 00:48 | If I'm on iOS, I'd press the Home
button and switch to another app.
| | 00:52 | Maybe I get a call, maybe my battery runs out.
| | 00:54 | If I'm on the Mac, I could quit the app or shut down my
machine, but I leave the application. So what happens?
| | 01:00 | Well, all these objects just go away.
| | 01:03 | Unless I've provided some code, some way
of saving them they will just disappear from
| | 01:08 | memory, and the next time I open the
app we start again from the beginning.
| | 01:13 | There are a couple of exceptions.
| | 01:15 | If you're working with iOS, a value entered
into a standard user interface element will
| | 01:20 | be kept in the background state.
| | 01:22 | So that app can be quickly reopened.
But that's just convenience.
| | 01:25 | That won't survive a reboot of the device, and it
certainly won't save any objects behind the scenes.
| | 01:31 | This behavior is not always a problem.
| | 01:34 | Something like a calculator app
might be just fine that way.
| | 01:38 | When you finish, it forgets everything, but
when you start you always start from the beginning
| | 01:43 | as if it was the first time
you'd run the application. We know this.
| | 01:47 | We know we sometimes want to save.
| | 01:50 | But it's very easy to think of saving as
something you do for documents or spreadsheets,
| | 01:55 | large files that you give a name to.
But in fact, it's much wider than that.
| | 02:00 | You expect that when you turn off your iPhone,
you still have your photos, your mail, your
| | 02:04 | settings, your music, the cities in your weather
application, the stocks you look up in your stocks
| | 02:09 | app, the high scores in your games or what
level you got to, the songs in GarageBand,
| | 02:13 | and of course, yes, the documents
and spreadsheets you've written.
| | 02:18 | So as a developer, we need the state of our
objects, the information in our app, or at
| | 02:23 | least some of it the most important information to be
savable to not require the application to be running.
| | 02:30 | This is what we mean by the term
persistence, that this data can persist.
| | 02:34 | It can outlive any process that is using it.
| | 02:37 | Now I am talking here first about
persistent locally, saving directly to the storage on
| | 02:42 | an iOS device or saving to the hard drive on a
desktop or laptop and being able to quickly
| | 02:48 | read that information back
when the application is opened again.
| | 02:53 | But of course, we do
already have ways we can do this.
| | 02:56 | There are multiple ways I can deal with
this problem without having Core Data.
| | 03:00 | And it's worth talking about those for
just a second, what are the alternatives?
| | 03:05 | Well, alternative number one, do nothing.
This is your default.
| | 03:09 | Don't do any saving of your data, fine for
some apps, not fine for most app once they
| | 03:13 | do anything remotely interesting.
So we will talk no more on this.
| | 03:18 | The second option, good old flat files,
plain text, XML, property list files.
| | 03:23 | You can write code to save your data to these
files. Probably the most useful and convenient
| | 03:28 | in Apple development are property list or
Plist files as several of the basic built-in
| | 03:34 | objects, like NSStrings, NSDates, NSDictionary have
methods to write their contents directly to Plist file.
| | 03:41 | So this is very simple as long as you have
very straightforward data, but you can't just
| | 03:47 | write any object into a Plist.
| | 03:49 | So it's not good if you have
complex objects or a lot of objects.
| | 03:53 | The next alternative would
be archiving and unarchiving.
| | 03:57 | If you have more complex custom objects, you
can write your new classes to support NSCoder,
| | 04:03 | which means in your class, you are providing
methods for encoding and decoding your objects,
| | 04:08 | which really just means writing code to break
your objects apart into their individual pieces of
| | 04:14 | data so that they can be archived or
stored into a flat file and then unarchived from
| | 04:19 | that flat file back into an object.
| | 04:22 | One popular option for persistence is SQL Lite,
or SQLite, a lightweight embedded relational
| | 04:29 | database engine that is
already available in iOS.
| | 04:32 | It's built into the SDK, and it's
available on other platforms too.
| | 04:36 | So in your application, you'd write code to
create and connect to database tables, then
| | 04:41 | to save and read from those
tables using SQL statements.
| | 04:46 | Now one benefit is that many developers already
know SQL and SQL, but there's a couple of downsides.
| | 04:52 | Working with SQLite requires C
calls rather than Objective-C.
| | 04:56 | So that can be a little tedious, and it
still really needs us to pull our objects apart
| | 05:01 | to be able to put them in a relational database.
| | 05:04 | As we'll see a little later we will often
use SQLite with Core Data, but that is simply
| | 05:11 | using it behind the scenes as a
storage format and is transparent to us.
| | 05:16 | We don't need to know the
details of SQLite to do that.
| | 05:20 | The last alternative I am going to talk
about here is to use something like iCloud or
| | 05:24 | web services to save your data over the
network to a remote location, using formats like SOAP
| | 05:32 | or JSON and fetching it back again later.
| | 05:35 | But even if you're interested in using cloud-
based storage, if you completely rely on this,
| | 05:40 | it'll mean that if you have no
network connection, you have no data.
| | 05:44 | So people often want persistent data locally,
even if they are fetching it from a server.
| | 05:50 | Now with all of these alternatives
to Core Data, you have to do some work.
| | 05:54 | Some of them like property lists are very
easy to get started with, but the more data
| | 05:59 | you have and the more complex that
data becomes, the more work you have to do.
| | 06:03 | Now here is the biggest difference, though.
| | 06:06 | These methods that I have just described
all expect you to break your objects apart.
| | 06:11 | We might use terms like serialization and
deserialization, or archiving and unarchiving,
| | 06:17 | encoding or decoding, but the idea is the same.
| | 06:20 | You are writing code to break them apart,
flatten your objects out. Stop thinking in
| | 06:25 | object-oriented terms in order
to able to save your objects.
| | 06:29 | Well, Core Data is different.
| | 06:32 | Core Data will let us continue to work with
objects, to think in objects, to be object-oriented.
| | 06:39 | So it is a more relevant way, a more natural way
of doing persistence than any of the alternatives,
| | 06:45 | and because of that, it will also provide more for us
than just saving our data, as you'll see in a moment.
| | Collapse this transcript |
| Understanding what Core Data provides| 00:00 | The aim of Core Data, the way that it's
different is that it allows us to take the state of
| | 00:05 | all the interconnected objects in our application,
meaning our object graph and just saying save
| | 00:12 | and not iterating programmatically through
all of these objects one at a time calling
| | 00:17 | save on each one and then trying to
re-create those exactly as they were saved,
| | 00:22 | but instead being able to instantly take
the whole thing and just freeze dry it in place
| | 00:27 | and store it to take a snapshot and store it
on the device or the laptop hard drive.
| | 00:32 | Well, I say save everything, but you know,
typically you're not really interested in all the objects.
| | 00:38 | You are only interested
in the important objects.
| | 00:41 | You don't usually need or even want to save
every single object in a running application
| | 00:46 | because you'll often have lots of temporary
working variables, and things like user interface
| | 00:50 | objects are already stored in your NIB files.
So a subset of objects, the important stuff.
| | 00:57 | Typically the M of MVC of Model View Controller,
meaning your model objects created from your custom classes,
| | 01:04 | the information that's unique and
important in your application, whatever that is.
| | 01:10 | To discard the temporary objects, persist
your objects and then when the application opens
| | 01:16 | again, just quickly load them back in and
have everything reconstituted, all the objects,
| | 01:21 | their data, and their relationships with
other objects re-created exactly as they were, and
| | 01:26 | this is what Core Data does.
| | 01:29 | You are not writing a whole bunch of code to
deal with archiving and un-archiving, flattening
| | 01:34 | objects out, reconstructing them, worrying
about how one object connects to another object,
| | 01:39 | that's all taken care of by Core Data.
| | 01:42 | So your app starts running again, and then
you just create any of the additional objects
| | 01:46 | it needs that time around, and this is what is meant with
the phrase Core Data is an object graph persistence framework.
| | 01:54 | It's not about saving unconnected independent
pieces of data, it's about saving a snapshot
| | 01:58 | of your objects at a point in time, whether
that's one object or a thousand interconnected
| | 02:04 | ones, that is what Core Data provides.
But wait, there is more, that's only part of it.
| | 02:10 | See, when you use the other options for
persistence, here's how development usually goes.
| | 02:15 | You create an app and write
a couple of custom classes.
| | 02:18 | You then write some code to save and load
them from some conventional store property
| | 02:23 | list files, SQLite, whatever that store is.
| | 02:26 | Now that part, just getting started saving to
property list files or SQLite, is usually pretty easy.
| | 02:33 | But after that, things rapidly
take a more difficult turn.
| | 02:36 | As your application becomes more complex,
well, first you're going to start adding some
| | 02:41 | validation code to make sure the objects
aren't ever re-created in some invalid state, and
| | 02:46 | that's particularly important when you
have objects that reference other objects and
| | 02:50 | you need to guarantee all those
references are still meaningful.
| | 02:54 | Next, you might write some code to handle undo and
redo to be able to back out changes to those objects.
| | 03:00 | Make sure that undoing a change to one
object doesn't invalidate another one, and if you
| | 03:05 | have a lot of data, you don't just want to
load it all up front because that's going to
| | 03:09 | delay the start-up time
and be bad for performance.
| | 03:12 | So you'll start writing code to try and load
data more efficiently only when needed, what's
| | 03:16 | sometimes called Lazy Loading, and after that
we might need some code to be able to search
| | 03:21 | all this data that you are storing.
| | 03:23 | With most persistent options, most choices
like property lists, flat files, SQLite, here's
| | 03:29 | how these responsibilities break down.
| | 03:32 | This is what your storage mechanism takes care of,
which is pretty much nothing except storing the data.
| | 03:39 | And all this, everything else is you, it's
your responsibility to write, it's what you
| | 03:44 | need to do in your application. That will
be the case for property lists, XML files
| | 03:48 | using classes with NSCoder method using SQLite.
| | 03:52 | But when we use Core Data, we turn that on
its head because all of this is in Core Data,
| | 03:59 | and then this is you.
| | 04:01 | When you opt into using Core Data, you get a
huge amount of functionality provided for free.
| | 04:07 | Core Data doesn't just help you store your
information, it can validate and keep validating
| | 04:11 | your objects over their lifetime.
| | 04:13 | It helps you manage changes to your objects
with undo and redo support built right in.
| | 04:17 | We have lazy loading only loading what's
necessary rather than reconstituting everything
| | 04:22 | into active memory at once.
| | 04:23 | It can help you search through all the
data you have and only return what you need.
| | 04:27 | And it does more, but that's
enough to get started with.
| | 04:30 | And this is why when people ask
things like is Core Data a database?
| | 04:34 | The answer is no, it's more than that.
| | 04:36 | Sure, it might take care of storing the data
like a database would, but it also adds all
| | 04:41 | the rest of this functionality in a way that
other persistence options don't, and it does
| | 04:45 | this all in a way that's inherently object-oriented
and easy to connect to iOS or Cocoa user interfaces,
| | 04:52 | and it's already written.
| | 04:53 | We don't need to build
Core Data, we need to join in.
| | 04:58 | We need to hook into this Core Data machine
that's already running, and that is our challenge,
| | 05:04 | hooking our application into Core Data,
and here's how we're going to do it.
| | 05:09 | To learn how to hook into Core Data in
this course, we will do it in four phases.
| | 05:14 | The first phase is describing or modeling.
| | 05:17 | The most important thing to get started is not
writing code or creating user interfaces, it's modeling.
| | 05:24 | Creating a data model of our most
important objects in our application.
| | 05:28 | It's us describing to Core Data what's in
our app that's important, what objects we
| | 05:34 | want it to take care of.
| | 05:36 | Data modeling is one of those terms
that can be used in an abstract fashion.
| | 05:41 | Something you think about
doing on a whiteboard or on paper.
| | 05:43 | Now that is not what it means here. We will
have an actual data model file in our Xcode
| | 05:49 | project that's as important and specific as
a ViewController Class file or a NIB file.
| | 05:55 | It is truly part of our application, and any
change to the model is a change to the app.
| | 06:01 | Now once we've done that, we'll move
on to telling Core Data how to save,
| | 06:06 | how at the time of our choosing we can take
the modeled objects in our application, our
| | 06:10 | object graph and persist it, save it. We'll
see the classes and the methods that we need
| | 06:14 | to use to do this and the options that we have.
We'll then move on to phase three of fetching,
| | 06:20 | how to fetch or retrieve the data that
we've saved, how we can quickly filter it,
| | 06:24 | sort it, ask questions of it.
| | 06:26 | Now, if you're coming from a conventional database
background, know that this is not done using SQL.
| | 06:31 | It's done using something called Predicates,
and we'll see how to make and use these,
| | 06:35 | and when needed, ways of putting the
resulting data into our user interface elements.
| | 06:40 | And phase four, everything else.
| | 06:42 | This covers a lot, but once we know how to
model to save and to fetch from Core Data,
| | 06:48 | we have a great foundation for everything
else, undo and redo, performance options like
| | 06:53 | lazy loading, versioning our applications
importing data, and much more, and more complex
| | 06:59 | modeling, saving, and fetching.
| | 07:00 | And we'll also revisit that Core Data stack
diagram when it will make a lot more sense.
| | 07:05 | I have been very concept-heavy these last
couple of movies to describe what I think
| | 07:11 | is the best way to approach Core Data and avoid
some of the common misunderstandings I've seen.
| | 07:16 | But we are going to get hands-on right now.
| | 07:19 | I'm going to jump into Xcode next and do a
quick demo using Core Data where we will see
| | 07:24 | the first of these three phases: modeling,
saving, and fetching, and we'll start to get
| | 07:29 | familiar with the vocabulary and the terms
we're going to use a lot, now that we're living
| | 07:32 | in the world of Core Data.
| | Collapse this transcript |
| Creating a Core Data project| 00:00 | The easiest way to get started with
Core Data is to do almost nothing.
| | 00:04 | Now you might think, great,
I've been doing that already.
| | 00:06 | But let me show you what I mean.
| | 00:08 | Jumping into Xcode, I'm
going to create a new project.
| | 00:11 | Now Core Data is a framework that we can
link to and use in any iOS or Cocoa application.
| | 00:17 | And if you have an existing project which
you've written so far without Core Data, then
| | 00:21 | yes, you absolutely can add
Core Data support to that project.
| | 00:26 | But when you're first learning Core Data,
learn it by creating some new projects, even
| | 00:30 | if you end up just deleting them.
| | 00:31 | This is a much easier way to get all the
pieces organized in your head before you try and
| | 00:35 | retrofit those pieces into an
existing project with a lot of code.
| | 00:40 | So in Xcode, three of the built-in project
templates for iOS have an option, a check
| | 00:45 | box to include Core Data support.
| | 00:49 | And so does the regular Cocoa
Application for desktop development.
| | 00:54 | And the benefit of checking this is not
that our project will link to Core Data.
| | 00:58 | We could do that anytime.
| | 00:59 | There's nothing magical
about linking to a framework,
| | 01:02 | but that it will also add some code and a
new file to this project to get us up and
| | 01:06 | running with Core Data. It
will simply save us some time.
| | 01:09 | And I can demonstrate some code here,
without you having to watch me type it all in.
| | 01:13 | So I'm going to do this first demonstration
using an iOS application because I suspect
| | 01:17 | that's slightly more of my audience here,
but we will see some Cocoa-specific examples
| | 01:22 | later, and the basic
techniques are the same for both.
| | 01:26 | So in Xcode, the three templates for iOS that
have a Core Data option are the Master-Detail
| | 01:31 | Application, the Utility Application,
and the Empty Application.
| | 01:35 | And usually it's not an option when you're
creating a good old single-view application,
| | 01:40 | or at least not at the time
that I'm recording this course.
| | 01:43 | There is no Core Data check box here. Now,
this does not mean that single-view apps are
| | 01:48 | prohibited from using Core Data. They
don't have a problem using Core Data at all.
| | 01:52 | But you just have to add it yourself manually, linked
to the framework and manually add the necessary code.
| | 01:57 | And by the time you're done
with this course, you'll know how.
| | 02:00 | But to begin with, I'm
going to take the easy option.
| | 02:02 | I'm going to create an iOS Master-Detail
Application, click Next, I'll call this CoreDataDemo.
| | 02:11 | I'll select it for the iPhone. I'll use a
couple of the modern development techniques.
| | 02:15 | We are going to have Storyboards checked and
Automatic Reference Counting checked.
| | 02:20 | Everything I do in this course is going to be with
arc turned on, and of course, the important one
| | 02:25 | Use Core Data, not going to bother with Unit
Tests so that will be unchecked, and going
| | 02:30 | ahead, I'll save this to my Desktop, and I'm
going to make sure that the local git repository
| | 02:35 | is unchecked as well.
| | 02:38 | And then I'll just go
ahead and run this application.
| | 02:41 | This does not actually require anything else. It
is a working albeit simple Core Data application
| | 02:47 | right out of the box.
Everything is already in place.
| | 02:51 | So the application opens, and if I press the
plus button, what I'm doing here is instantiating
| | 02:56 | a new although very simple object, this object
just has a timestamp property, and I'm showing
| | 03:02 | these objects in this table view here.
| | 03:05 | I can select any of them to drop into the
detail view which shows me the details for
| | 03:09 | just a single object.
| | 03:11 | Again, it's just a timestamp and then back
up to the table view, and I can swipe any
| | 03:16 | one of these and delete that object.
| | 03:18 | It doesn't look remarkable, but every time
I'm creating one of these objects it is being
| | 03:23 | saved, persisted by Core Data.
| | 03:26 | So I would expect that if I leave this application
and come back in that I would see those objects again.
| | 03:32 | But let me demonstrate something else.
| | 03:34 | Now you don't need to duplicate this, but I'm
going to show it because you might find it useful.
| | 03:38 | On the simulator, I also have an app that I
created a little earlier that is the regular
| | 03:44 | iOS Master-Detail app but without Core Data.
I've just called it WithoutCD.
| | 03:49 | Now if I open that up, it looks pretty much identical.
I can use the plus button and add, say, three objects.
| | 03:55 | I can drop in and see the detail view.
I can go back up and swipe to delete.
| | 04:01 | And if I go out of this WithoutCD and back in, I
see those objects. It looks like it's saving data.
| | 04:08 | But really here, this is just the behavior
of iOS going to the background and foreground
| | 04:13 | modes of this application.
| | 04:15 | If I actually truly terminate this app--and
one easy way I could do here is just double-click
| | 04:20 | the Home button, hold down and click the
minus button here and then, open it up again--
| | 04:26 | we have nothing. There is no real
persistence going on here. Nothing is being saved.
| | 04:31 | But on the other hand ,if I do that with the
Core Data app, and first what I'm going to
| | 04:36 | do is just stop it running in Xcode because
I don't want Xcode to get a little confused
| | 04:40 | if I terminate the application.
I open it up, we see these five objects.
| | 04:45 | I close it down, I'm now going to double-click
and both terminate the Core Data version and
| | 04:51 | the non-Core Data version.
| | 04:53 | Without Core Data we have nothing,
with Core Data we have persistence.
| | 04:58 | These objects are being saved to the file
system of the iOS device, in this case the simulator.
| | 05:04 | These objects are not dependent on
the lifetime of the app that uses them.
| | 05:08 | So going back into Core Data, what is the
difference, then, with a Master-Detail project
| | 05:13 | with the Core Data check box and Master-Detail
project without that Core Data check box?
| | 05:18 | Well, if I were to look
through a couple of them and compare,
| | 05:21 | I would see that the storyboard is exactly
the same. It's a Navigation Controller that
| | 05:26 | contains a Master View Controller with a table
view that just goes down to a Detail View Controller.
| | 05:32 | All the code for the Detail View Controller, if you
compared and contrasted, would be exactly the same.
| | 05:38 | What's different is three things.
First, we're linking to the Core Data framework.
| | 05:43 | Second, some code has been added to the app
delegate and to the Master Detail View Controller.
| | 05:50 | To make sure that the table view is being
fueled by Core Data rather than just being
| | 05:55 | fueled by a mutable array, which is the way it
would be in a regular iOS master detail project
| | 06:01 | template without Core Data support.
| | 06:03 | Now this code uses several of the Core Data
specific objects and is likely to look a little confusing.
| | 06:08 | But that's okay, this is not
where we need to look first,
| | 06:11 | because what we're interested in is
the final addition to this project.
| | 06:15 | But there's one new file in the project
which is this one, CoreDataDemo.xcdatamodeld, the
| | 06:23 | Xcode data model file.
This is our starting point with Core Data.
| | 06:28 | Before we get into writing code, before we
start seeing how to save, how to fetch, undo/redo,
| | 06:32 | we need the data model.
| | 06:36 | This file represents the contract between
our application and Core Data, where we tell
| | 06:41 | Core Data what it's expected to manage.
| | 06:43 | Now in this case, it is a
really simple data model.
| | 06:46 | What its doing is describing something
called an event entity which has a timestamp, and
| | 06:52 | this is what's being created
instantiated as an object in our application.
| | 06:57 | But as we'll see, this data model could be
a lot more complex, and it's this, the data
| | 07:01 | model idea, this editor in Xcode, these terms you
see like entities and attributes, relationships.
| | 07:08 | This is what we're going to look at in the
next section our first phase of working with
| | 07:12 | Core Data, which is modeling.
| | Collapse this transcript |
|
|
2. Modeling in Core DataIntroduction to data modeling in Core Data| 00:00 | So we've seen that the idea with Core Data is
that we'll be able to take a group of objects
| | 00:04 | and just say save, that could be one
object, two, or a hundred interrelated ones.
| | 00:10 | But I did mention that we don't need to save
every single instantiated object, so that would
| | 00:13 | be a waste of time and resources that there
are many objects in a running application
| | 00:18 | we'll never need to persist, temporary variables,
| | 00:22 | user interface objects that are already defined in
our storyboard, or NIB files, so we can ignore those.
| | 00:28 | Really, it's about our own custom classes that we've
written that will become our model objects, our data.
| | 00:34 | Whatever that data is, simple text
objects, complex binary objects,
| | 00:38 | but the objects we want Core Data to save.
| | 00:41 | Or you know, the best word here is
not save or even persist, but manage.
| | 00:47 | These are the objects we want Core Data to
manage, to manage their saving and loading,
| | 00:51 | to manage their undo and redo,
to manage their lifetime.
| | 00:55 | So these are what we will
call our managed objects.
| | 00:58 | There are still Objective-C
objects with methods and properties.
| | 01:03 | We can use them as we'd use any object, but
we'll be managing their lifetime with Core Data.
| | 01:08 | And when you see this term managed anywhere
in iOS or Cocoa development, you might as well
| | 01:13 | silently insert the words
Core Data in front of it.
| | 01:17 | There are classes like NSManagedObject, NSManagedObjectModel,
NSManagedObjectContext, and if you see that term,
| | 01:24 | assume that's what's doing the managing
is Core Data. These are all Core Data classes.
| | 01:31 | So when you create a project in Xcode that has Core
Data support, and it includes that xcdatamodel file,
| | 01:39 | this is the way that we tell Core Data which
objects in our application we want it to manage.
| | 01:44 | So this is not meant to be a model or a description
of everything in our app, just the managed objects.
| | 01:49 | And in fact, another name for this file rather
than just data model is managed object model,
| | 01:56 | and if we want something to be managed by
Core Data, it needs to be part of this data model.
| | 02:00 | The flip side of this is if it's not in
this model, it is not managed by Core Data.
| | 02:06 | So one question I'll get as well, if you've
got some managed objects and other ones that
| | 02:11 | we might think of as unmanaged objects, how
do you tell Core Data which are the regular
| | 02:15 | unmanaged objects that it's not
supposed to touch? Well, you don't.
| | 02:19 | You don't do anything with them.
| | 02:21 | So for an example, if you take an existing
project and just link to the Core Data framework,
| | 02:25 | it will not suddenly attempt to start persisting all
your regular NSStrings and NSArrays and custom objects.
| | 02:31 | Core Data won't touch anything unless
it's described in the model.
| | 02:37 | Now when we use the word modeling,
particularly things like data modeling or object modeling,
| | 02:41 | it's often used to suggest a graphical
approach, although that isn't necessary.
| | 02:45 | Although yes, this same editor part of a
project in Xcode can switch between a graphical view
| | 02:52 | and a table view of the same information that we'll
see a little later, but know that this is not for show.
| | 02:59 | Sometimes with other programming environments, if you have
a modeling tool it's kind of an add-on, an optional extra.
| | 03:05 | You might use it to chart out some class
diagrams and at some point perhaps even have it generate
| | 03:10 | some skeleton code, but it won't actually
affect your project if you don't want it to.
| | 03:15 | Well, not in Xcode. This data model
file is part of your application.
| | 03:20 | Any change to this model is a change to the app.
| | 03:23 | The same way that a storyboard file or XIB
file will be turned into instantiated objects
| | 03:29 | when your application builds
and runs, so will this model.
| | 03:32 | I said a little earlier that one name for
this is actually your managed object model.
| | 03:38 | Well, when you build and run this file, it will be
turned into an object called an NSManagedObjectModel.
| | 03:44 | Whatever you do in this data model
file will be accessible in your code.
| | 03:50 | Now some people wonder, could you then
create this data model programmatically instead of
| | 03:55 | using this file and this editor part of Xcode?
| | 03:58 | Sure. The same way that you could create, say,
an entire user interface purely in code, you
| | 04:04 | could create this data model in code as well.
| | 04:06 | But that would be amazingly tedious to
do and really doesn't have any benefit.
| | 04:11 | So we're going to be using this data model
editor for everything we do in this course.
| | 04:15 | And we're going to start off
with something called Entities.
| | Collapse this transcript |
| Creating entities| 00:01 | So there will be a data model file in any
project if you select Core Data as an option,
| | 00:06 | but it can also be added
manually to a project.
| | 00:09 | I'm just going to create a new master detail iOS
demo with Core Data, same as I did a moment ago.
| | 00:15 | So, finding that data model file and selecting
it will open the Model Editor part of Xcode.
| | 00:21 | There is quite a lot of options here, but
the first, the most important building block
| | 00:25 | of any data model is something called an Entity.
| | 00:28 | And an Entity is how you describe
the structure of your managed objects.
| | 00:33 | The same way that you'd write a class in
normal Objective-C that defines the structure of
| | 00:38 | regular objects you can then instantiate,
and we do continue to do that for regular objects.
| | 00:43 | Well, in Core Data, you'll define an entity that
describes the basic structure of managed objects.
| | 00:49 | So there could end up being 1,000 managed
objects based on any one entity, and we'll
| | 00:54 | have at least one entity, you may have many.
| | 00:57 | In this example, I have one entity showing
up on the left-hand section of the Data Model
| | 01:02 | Editor, this is called Event.
| | 01:04 | I don't have to keep this. It's
just the example from the template.
| | 01:08 | So in the left-hand section, I can select
it, and I'll see the details of that entity
| | 01:12 | in this middle section here.
| | 01:14 | And I can see that this entry seems to have
one thing, an Attribute called timeStamp and
| | 01:20 | timeStamp has a Type of Date.
| | 01:22 | So this application could have multiple
managed objects, all instantiated from this Event
| | 01:28 | entity where each managed
object will have its own timestamp.
| | 01:32 | Now down here at the bottom of this section,
I can add a new Entity to this, clicking the
| | 01:37 | button that allows me to go up here and name it.
| | 01:40 | I can also use the Editor menu in
Xcode when I'm in the Model Editor.
| | 01:45 | So I can add entity, and I start to see them
add up over here in the left-hand section.
| | 01:51 | I've also got this Fetch Requests and
Configurations areas, we'll get into these later.
| | 01:56 | We don't need them now, but
we will always need Entities.
| | 01:59 | And you'll typically name new entities like
classes an uppercase first letter, singular
| | 02:05 | noun, so Event, Book, Author,
Employee, Account, Player, Person.
| | 02:11 | And if you're thinking, well, how am I
supposed to know what my entity should be in my app?
| | 02:15 | Well, the question is,
what would your classes be?
| | 02:17 | If you weren't using Core Data, these entities
would be your normal classes for your model objects.
| | 02:23 | So if you are building a bookstore application,
you might have classes for book and publisher
| | 02:27 | and author, you'd have entities for those.
| | 02:30 | If you're making a music application, you'd
have entities for album and artist and track.
| | 02:36 | And if you're wondering, why do I have
to use this new concept of Entity?
| | 02:39 | Why can't I just deal with regular Objective-C classes and objects?
Well, realize that we're doing something different here.
| | 02:47 | An entity might be at the same conceptual
level as a class, but unlike a class, we are
| | 02:52 | not trying to define behavior here.
| | 02:54 | So what we're doing right now is not like,
say, a UML class diagram. We are not defining
| | 03:00 | method names, any behavior. An entity
is pure data, no behavior, no logic.
| | 03:06 | But you might say, what if I want these managed
objects, these things that Core Data is going
| | 03:12 | to save, what if I want
them to have custom behaviors?
| | 03:14 | Well, we will get to that. Yes, absolutely,
they can have custom behavior, but that is
| | 03:20 | not defined in the Entity.
The entity is data.
| | 03:24 | You're simply describing what Core Data will
store, what it will persist, what it will manage.
| | 03:29 | Now if you're coming from a relational
database background, you might see that what we seem
| | 03:34 | to be doing here looks a little similar to
defining a schema for a database where our
| | 03:38 | entities are like tables and the attributes
look like columns in those tables, describing
| | 03:43 | what kind of data those columns can hold.
And that is true, there are similarities, but
| | 03:48 | it only goes so far.
| | 03:50 | So don't assume all those skills directly
apply here, because if you start worrying
| | 03:54 | about things like primary keys and
foreign keys, you're going down the wrong path.
| | 03:58 | It's simpler than that here.
| | 04:00 | However, entities certainly can be
more complex than just the timestamp.
| | 04:04 | So next, we'll take a look
at our options for attributes.
| | Collapse this transcript |
| Creating and configuring attributes| 00:00 | So, let's see how to work with attributes.
| | 00:06 | I'm going to just go ahead and create a new
master detail project, again, using Core Data.
| | 00:11 | As we've seen, selecting any entity in our data
model gives us these three sections on the right.
| | 00:17 | These are representing the three kinds of
property that every entity consists of: Attributes,
| | 00:22 | Relationships, and Fetched Properties.
| | 00:25 | And this is pretty much the order of their
importance, and the time that you'll spend with them--
| | 00:28 | by far the most important is the
Attributes where we define the data of each entity.
| | 00:33 | There are relationships between entities, but
there don't have to be any in bringing up
| | 00:37 | the rear fetched properties.
| | 00:39 | These are a little more obscure, and we'll
come back to these when we talk about fetching.
| | 00:43 | So what we're going to
work on here are attributes.
| | 00:45 | Now there is such thing as an
attribute that's floating out there by itself.
| | 00:50 | All attributes belong to an entity.
| | 00:53 | Right now, we have one called
timeStamp with a type of Date.
| | 00:56 | Every attribute must have a name and a type.
| | 00:59 | And just as by convention, the entity is
named like a class, the attributes are named like
| | 01:04 | class properties or instance variables,
typically camel case like the timeStamp here.
| | 01:09 | And again, that's because they're comparable.
| | 01:12 | Classes and entities exist at the same level,
although entities are just about data, but
| | 01:17 | we expect to have objects based on both,
| | 01:20 | regular objects based on regular classes,
managed objects based on entities.
| | 01:25 | Now if I click the Plus button here to add
a new attribute--and I can also do that from
| | 01:30 | that Editor menu of Adding Attribute--
we'll see it entered here.
| | 01:35 | An attribute will default to a name
of attribute and a Type of Undefined.
| | 01:39 | Now it's not acceptable to leave it
as Undefined, and you actually can't.
| | 01:43 | Xcode will give us a compile error
instantly if we try and leave it as undefined.
| | 01:48 | So let's say I wanted to call this
attribute title, and then in the Type we have options
| | 01:54 | of String and Boolean and Float,
Decimal, Double, several integers.
| | 01:58 | And most of these data types should be no
surprise at all, several numeric options,
| | 02:03 | Bool, strings, date, and these are
all just the classes you would expect.
| | 02:07 | Strings will be NSStrings, dates will be NSDates, and all
the numeric options are actually wrapped up in an NSNumber.
| | 02:15 | We'll see how to use them shortly, but
these should take care of most problems.
| | 02:18 | Now towards the bottom are a couple of
options that are a bit more complex.
| | 02:23 | There's a Binary Data option, and that would
allow you to store images or audio, any kind
| | 02:29 | of binary data in your managed objects.
| | 02:32 | And because Core Data can't list everything,
there's also a Transformable type for your attributes.
| | 02:38 | Now Transformable would allow you to use a
custom type here as an attribute, some kind
| | 02:43 | of custom structure that
it can't envision right now,
| | 02:46 | although what you'd have to do is you would have
to provide something called a Value Transformer
| | 02:50 | to explain to Core Data what this attribute is made
of and how it's supposed to get data in and out of it.
| | 02:57 | So we'll be focusing with the basic types here
as they're going to work for us in most cases.
| | 03:02 | So let me go ahead and just add a couple
of new entities and some attributes to them.
| | 03:06 | I'll Add a new entity, I'll call it Book.
| | 03:09 | Selecting that on the left-hand side--and
sometimes it's worthwhile switching off and back on
| | 03:13 | to make sure I'm getting the attributes.
| | 03:15 | Right now, I wouldn't expect to see any.
I'm just going to add a couple here.
| | 03:18 | We'll say title, which is type of String. I'll
add an attribute for author and publicationDate.
| | 03:30 | One thing to be aware of is it will
try and reorder them alphabetically.
| | 03:33 | PublicationDate should be a Date,
and author and title should be Strings.
| | 03:39 | Another example, adding another
Entity, I'll call this one Employee.
| | 03:49 | Now all I'm doing here are a few examples
just to show you the kind of idea what you
| | 03:54 | have to pay attention to.
| | 03:55 | I'm not actually going to go ahead
and use these entities right now.
| | 04:00 | So I'm saying here department, firstName, and
lastName are Strings, isFullTime is a Boolean,
| | 04:05 | and hireDate is Date.
| | 04:07 | I'm demonstrating it to show that what
drives the attributes of an entity in a Core Data
| | 04:12 | data model are exactly the same choices
you would make for creating the properties of
| | 04:16 | a typical Objective-C class.
| | 04:18 | If I was creating this as a regular class,
I'd have properties for department, firstName,
| | 04:23 | lastName, they would be NSStrings.
| | 04:25 | I'd have a Boolean property for
isFullTime and have a NSDate property for hireDate,
| | 04:30 | because these attributes we're defining here
will become the properties of an instantiated
| | 04:35 | managed object in our application, and we'll just
access them like using the properties of any object.
| | 04:41 | However, one effect of that is if say I try
and create an attribute called description,
| | 04:46 | I'm going to have a problem because description
already exists as a property for every object in Objective-C.
| | 04:53 | It's part of NSObject, so that wouldn't be
allowed. I'd have to call that something else
| | 04:57 | like summary or outline.
| | 04:59 | But I actually don't need it there.
I'm just going to get rid of it.
| | 05:03 | Now when you're editing in the data model,
pay close attention to what you have selected,
| | 05:08 | because the Utilities Panel on the right, which
is of course context-sensitive as usual, means
| | 05:13 | that it changes not just based on whether
you have an entity selected in the data model
| | 05:18 | editor but also a particular
attribute of the entity.
| | 05:22 | Now we can't see much of a change right now,
because what I'm really interested in--and
| | 05:27 | when we are editing in the data model this
is the one we almost always want--is the last
| | 05:32 | inspector, the Data Model Inspector.
| | 05:35 | And you'll see that change as I change just not
from attribute to attribute but also entity to entity.
| | 05:43 | And with an attribute selected,
well, we have a bit more to play with.
| | 05:47 | We have another right here of changing the
Attribute Type, same options but just a different
| | 05:51 | way of getting to them.
But we also have some options below that.
| | 05:55 | So, say if I have a numeric attribute of some kind,
like Decimal, we can have Minimum and Maximum values.
| | 06:02 | We can specify a Default
value for our attributes.
| | 06:06 | And that understands the type we're
working with, so say in my Employee entity I had an
| | 06:11 | isFullTime of Boolean, I can get
the Default value here of YES or NO.
| | 06:17 | What that means is that in our app when a
managed object is instantiated from this entity
| | 06:22 | description it will have those default values.
| | 06:25 | You can also make any of these attributes
Optional so that it doesn't have to be a value.
| | 06:30 | If I jump over to a String attribute,
I'll also see default values here.
| | 06:34 | We can have some validation for the Minimum
and Maximum allowable length of the string
| | 06:39 | and even enter a Regular Expression
that a string value would have to match.
| | 06:43 | And these are just the beginnings of letting
Core Data manage the validity of our objects.
| | 06:48 | What it means is that in our code we want
to tell Core Data to save a bunch of objects,
| | 06:53 | it will inform us whether we're
trying to save any invalid data.
| | 06:56 | There is also an option for an attribute to be Transient.
It's off by default, and you'll usually leave it off.
| | 07:03 | But what it means is that any value in this
attribute will not be saved when Core Data
| | 07:09 | saves the manage object.
| | 07:10 | So in the occasional situation where it's
useful to have an attribute that you can manipulate
| | 07:15 | as long as the app is running but you don't
actually need to save it, perhaps there's a
| | 07:19 | value that's completely dependent on the
time and date that you're running the app.
| | 07:23 | Now, we'll come back to Transient and to a
few of the other options later in the course,
| | 07:28 | simply because we won't be able
to see their impact for a while.
| | 07:31 | Working with these basic attributes and
regular data types gives us enough to continue.
| | 07:35 | Now one question I do get, particularly
from developers who are looking at this like a
| | 07:39 | Database Schema Editor is, well, okay, after
I've defined a few basic structures for some
| | 07:45 | entities, where do I go to type in a
bunch of values for this? Well, you don't.
| | 07:50 | This is a way that we're
modeling the structure of our data.
| | 07:54 | It is not a way to enter any of that data.
| | 07:57 | Creating these objects, managed or
otherwise is done in our application.
| | Collapse this transcript |
| Modeling relationships| 00:01 | When you have more than one entity, you can
add relationships between them as long as
| | 00:05 | that makes sense, of course. Not all
entities need to be related to all other entities.
| | 00:09 | And by describing those relationships, when
our application runs, our managed objects can
| | 00:14 | have references to the other managed objects
they are related to, and those references are
| | 00:19 | taken care of by Core Data.
| | 00:21 | So I have a couple of entities I've just created in
this simple sample project here, Book and Publisher.
| | 00:28 | And I have made these very straightforward
as the point of this is simply to demo this model.
| | 00:32 | I am not building a full
application out of them.
| | 00:36 | Publisher has name, address, city, state.
Book has author, title, publication date.
| | 00:42 | I want to describe the
relationship between these.
| | 00:46 | There is currently no relationship.
| | 00:48 | I can look at them either in the Table Style or
Graph Style, there is no link between the two.
| | 00:52 | But to describe a relationship,
first I describe it in plain language.
| | 00:56 | For my application, what does this mean?
| | 00:59 | Well, let's say in my app, every book has a
publisher, and a publisher may publish many
| | 01:05 | books, that's the rule.
| | 01:06 | Now bear in mind what I have
just described is two relationships.
| | 01:10 | There is a relationship from Book to Publisher,
and there is a relationship from Publisher
| | 01:15 | to Book, and that's important.
| | 01:18 | In Core Data it is typical to create two relationships
between your two entities, one from A to B,
| | 01:26 | and a separate one from B to A.
| | 01:29 | And here's where I'll make another suggestion to
those of you from a relational database background.
| | 01:33 | There are some similarities, but creating
relationships in Core Data is quite different
| | 01:38 | from relationships in a conventional RDBMS.
| | 01:41 | If I was thinking like a conventional database
designer here, I'd probably be assuming then
| | 01:46 | I need to add a Publisher attribute to this
Book entity so I can treat that as a foreign key.
| | 01:52 | And no, no, no, a thousand times no,
don't go there, it is simpler here.
| | 01:58 | We want to be able to say, you know, a book
has a publisher and a publisher has books.
| | 02:03 | I want to describe it at that level and
let Core Data figure out the technicalities.
| | 02:08 | Now in Core Data, relationships aren't
objects by themselves. You don't create a detached
| | 02:13 | relationship out in the ether and later link it
between two entities. Instead, every relationship
| | 02:20 | belongs to one entity,
it is part of that entity.
| | 02:23 | If I'm looking at this in Table view, I see
a Relationship section for each entity the
| | 02:28 | same way you see an Attributes section.
So relationships belong to an entity.
| | 02:35 | And that means you always need to be
conscious which entity you're starting from.
| | 02:39 | Now you can create relationships in
either the Graph View or the Table View.
| | 02:44 | I'm going to start in Table View because
it gives me what I think is the right focus,
| | 02:48 | I am looking at one entity at a
time, first Book, then Publisher.
| | 02:53 | So first, I will create the relationship from Book
to Publisher and then the one from Publisher to Book.
| | 02:58 | So with Book selected, I come down to the
Relationship section and click the plus button.
| | 03:03 | What I'm trying to do is point from Book to
Publisher, so I am going to select from this
| | 03:07 | dropdown destination that
I am pointing to Publisher.
| | 03:12 | That's it, the relationship is
described, that's all we have to do.
| | 03:17 | But okay, there is a little more we can do,
because relationships always have a name, and
| | 03:23 | the name is useful. We could have relationship,
relationship 2, relationship 3, but you don't want that.
| | 03:29 | The name of the relationship should
pretty much always refer to the destination.
| | 03:33 | Again, we are pointing from
Book to the destination Publisher.
| | 03:37 | So in this case, I will call
this relationship publisher.
| | 03:42 | Now the reason that this name is useful is
that this relationship will become a property
| | 03:48 | of any object created from this entity, just
as all the attributes will become properties.
| | 03:54 | So when I create an object from this Book
entity, it will have a property called title
| | 03:59 | and a property called author and a property
called publication date, NSStrings, and NSDates,
| | 04:04 | but we will also now have a property called
publisher which will be a reference to a Publisher object.
| | 04:11 | That probably sounds a little vague right
now, simply because we haven't created any
| | 04:15 | objects based on these entities yet.
| | 04:17 | So just assume, yes, the relationship name is
useful, and it's typically named for the destination.
| | 04:23 | Now there is also an option here called
Inverse, that's going to be important, but we can't
| | 04:28 | do anything with that yet, so
we'll see that one in a second.
| | 04:32 | And this describes one side,
Book's relationship to Publisher.
| | 04:36 | So now I will do the other side of
this relationship, Publisher to Book.
| | 04:40 | So selecting the Publisher entity, I click
the plus button for Relationships, I say this
| | 04:45 | is going to be named for where we are
pointing to, so I will call it book, the Destination
| | 04:49 | is to Book, and that's the relationship defined.
| | 04:54 | However, this relationship is a little different.
See, our first relationship was what's called
| | 05:00 | a two-one relationship, or one-to-one.
A Book has a Publisher.
| | 05:05 | Let's say those are the
rules for this application.
| | 05:08 | Any book has a single publisher, and that's the
default kind of relationship in Core Data, just a one-to-one.
| | 05:15 | But this relationship is different because a publisher
could have many books, doesn't have to, but could.
| | 05:22 | So making sure I have got my Utilities panel
and still making sure I have my Relationship
| | 05:26 | selected, because again, it is sensitive to that,
| | 05:29 | I can come over here into the Data Model
Inspector, and there is an option here to say this is
| | 05:34 | a To-Many Relationship. I'm pointing
to books. I could have many books.
| | 05:40 | Now there are a few other options
here for customizing this relationship.
| | 05:45 | We have got things like an Optional, Minimum,
and Maximum Count of the number of books that
| | 05:50 | might be returned for this.
| | 05:51 | We are going to come back to these
later in the course. We don't need them yet.
| | 05:56 | Now because this relationship does not
describe just a single book that a publisher could
| | 06:01 | have, but really plural, that books--I am
actually going to change the name of this
| | 06:05 | relationship and just name it in plural. I
don't have to do that, but that would be a
| | 06:10 | fairly common thing to do.
| | 06:12 | Meaning that when an object is created based
on this entity, it can have a property called
| | 06:17 | books that will be a collection of book objects.
| | 06:20 | Again, we haven't got that far. We
will see that kind of thing later on.
| | 06:24 | Now, switching to Graph view lets me see
the diagram of these two relationships.
| | 06:29 | They may be overlapping a little bit, so you
can move them around, and it will do its best
| | 06:33 | to make this legible and readable.
| | 06:36 | And what we are seeing here with these
relationships is a different kind of arrow.
| | 06:40 | With the single head here that's describing a
one-to-one relationship, this is Book pointing
| | 06:45 | to Publisher, but we have a different style for
a Publisher to Book, this is the double arrowhead,
| | 06:51 | suggesting it's a To-Many Relationship.
| | 06:55 | These can be selected in the Graph Editor
and edited in the Inspector. Sometimes it's
| | 07:00 | a little difficult to make sure
you're selecting the right thing.
| | 07:03 | So these relationships are defined,
but there is one last thing I need to do.
| | 07:07 | I have created these as two relationships
which is the way we have to do.
| | 07:11 | But they really are just different perspectives
of the same idea that publishers have books.
| | 07:16 | So what I'm going to do is explicitly tell
Core Data that the relationship from Book
| | 07:20 | to Publisher is the inverse of the
relationship from Publisher to Book.
| | 07:25 | It will still keep my rules of the one is
a To-Many and one is a To-One, that's fine.
| | 07:30 | I just want to say yes, they are connected.
| | 07:33 | And I can do this by selecting either of the
relationships, and if I'm in the Graph View,
| | 07:39 | I can use the Data Model Inspector and
select the Inverse Relationship here.
| | 07:43 | I can also do it in the Table Editor, either way
around, it doesn't actually matter here.
| | 07:48 | So selecting Publisher, I find the relationship
of books, and I say that the inverse is the
| | 07:53 | one from Book pointing to Publisher.
| | 07:56 | If I jump to my Book entity, I will see that
it's automatically selected the other inverse there.
| | 08:01 | If I jump into the Graph view, I
see that they've been combined into one.
| | 08:06 | They are still considered two relationships,
and I could actually pull them apart by just
| | 08:11 | turning off that inverse, but they both now
realize they are the inverse of each other.
| | 08:16 | Now while this is not technically essential,
it is highly recommended, and we do this to
| | 08:21 | affirm to Core Data that these are two
approaches to the same relationship and that a change
| | 08:26 | one way will have an impact the other way.
| | 08:29 | And what that lets us do is keep the object
graph more controlled and consistent, because
| | 08:35 | most relationships are bi-directional like
this, so it's very, very common to do this.
| | 08:39 | And actually, most of the time you'll get
a compile warning if you don't define the
| | 08:44 | inverse when it seems like there is one.
| | 08:46 | But those Inverse Relationships can't be selected
until you've created both relationships both ways.
| | 08:53 | So these are the basics.
| | 08:55 | We are going to get into more of the
options of these relationships later on.
| | 08:59 | Now of course, we're still at the point
where we can't see the full benefits of doing all
| | 09:03 | this work, as we don't have
the other pieces in place yet.
| | 09:06 | But defining relationships will let us
retrieve this information much more flexibly later
| | 09:10 | on and have a more complex object graph of
objects connected to other objects but with
| | 09:15 | Core Data maintaining the references between
them, rather than us having to keep track of that.
| | Collapse this transcript |
|
|
3. Saving in Core DataCreating managed objects| 00:00 | So we can take modeling
further than that, but let's not.
| | 00:04 | Right now, let's take what we know, make a
simple managed object model, and see how to use it.
| | 00:09 | Because the entire point of defining these
entities is that we are going to instantiate
| | 00:14 | managed objects based on those entities,
we're going to use those objects, and then we are
| | 00:19 | going to get Core Data to save them.
| | 00:22 | And that's what we are going to do in this
next section: create, use, and save managed objects.
| | 00:27 | And by the time we're done here, we'll naturally
run into a few more pieces of the Core Data puzzle,
| | 00:31 | things like context and store coordinators,
and we'll also answer the question if we are
| | 00:37 | saving these managed objects, where do
they actually go, where do they save to?
| | 00:42 | So let's get to it.
| | 00:43 | In Xcode, I am going to create a new Cocoa Application
this time, so under OS X select Cocoa Application.
| | 00:50 | If you have never done Cocoa Apps, don't worry,
you will be able to follow along just fine.
| | 00:55 | I am going to call it SaveDemo, and the
only thing I am going to pay attention to here
| | 01:00 | is just looking at the Company Identifier for
a moment. I have got com.company, that's fine.
| | 01:05 | It's not important what it is. I just want
to remember it, it will come in handy later.
| | 01:09 | I am now going to have an App Store Category,
Document-Based Application is unchecked, I
| | 01:14 | want Core Data checked, and Use Automatic
Reference Counting checked, and that should do it.
| | 01:20 | Click Next, and I'll just
save this to my Desktop.
| | 01:23 | Now when I make a Cocoa Application with
the Core Data check box selected, I don't get
| | 01:30 | a sample application the way I did
with a Master-Detail iOS template.
| | 01:34 | If I go ahead and run this,
there is nothing here.
| | 01:38 | We need to add everything that's necessary here, and I'm
going to make just a simple one-button application.
| | 01:44 | But if I go back into Xcode, I see that I do
have a data model file here in this project,
| | 01:49 | although it's completely empty, there is
no entities in it, and I do have some of the
| | 01:53 | Core Data code has actually been added into my
app delegate. We'll see that a little later on.
| | 01:58 | So I am going to jump into the model here
and just add a new entity. I'll call this
| | 02:04 | Course, and I'll just give it three attributes
to be simple here, title, author, and I'll
| | 02:15 | call it release date.
| | 02:17 | They are all undefined right now. That's not
acceptable, I want the title and author to be strings.
| | 02:23 | One thing I can do here is using Command,
I can click on title, then Command-click on
| | 02:27 | author and with both of these selected in the
Inspector change their attributes to string.
| | 02:33 | That can come in handy when you've got
four or five at a time you want to do.
| | 02:37 | Release date, I'll just change manually to date,
pretty much as simple an entity as I can imagine here.
| | 02:43 | Now what I am going to do is
jump over to the interface.
| | 02:45 | So I am going to click on the MainMenu.xib
file, and I want the main Window part of
| | 02:51 | this so I can select that in the dock, either
in the minimized or the expanded view, I just
| | 02:56 | want a Window so that I see it here.
| | 02:59 | From my Object Library and my Inspectors, I
am going to drag on a Push button and just
| | 03:04 | give this a name, shrink down the
window because it is pretty simple.
| | 03:12 | This button is going to instantiate a managed
object based on that entity I've just written,
| | 03:16 | and then change the properties of
that object and make sure it's saved.
| | 03:20 | I am just going to give myself a bit more
screen real estate here and switch into Assistant
| | 03:25 | View, and I should be bringing up my
AppDelegate on the other side here.
| | 03:31 | If you are coming from iOS, know that in
Cocoa I don't have to have a formal separate view
| | 03:36 | controller class file. I can just have all of my
code in the AppDelegate for a simple app like this.
| | 03:41 | So I am going to make this button just
call an IBAction here, so I'll Ctrl-click from
| | 03:46 | the button over down here in
the AppDelegate and let go.
| | 03:50 | I want to make sure I'm creating an Action, not an
Outlet, and I'll call it createObject and hit Connect.
| | 03:58 | That's everything I need to do in the
header for now. I am just going to give myself my
| | 04:01 | regular editor back and jump into the
implementation file for AppDelegate.
| | 04:08 | I'll use the jump bar to find that
new method I created, createObject.
| | 04:11 | Here is where I am going to
write all the code that I need.
| | 04:15 | Now normally when we instantiate an object,
we are going to use alloc init or some variant
| | 04:20 | of that, perhaps a helper method of a class,
where we'd have a couple of pieces of information,
| | 04:25 | the name and the type of the object
pointer we are creating and the name of the class
| | 04:29 | we are creating it from.
| | 04:31 | But when I am instantiating a managed object,
which is what I want to do right now, we need
| | 04:36 | a little different information.
| | 04:37 | Well, first, I do just want to
create a managed object pointer.
| | 04:41 | So I am going to start typing
NSManagedObject. There we go.
| | 04:46 | And I'll give it a name like
myMO for my managed object.
| | 04:51 | But I don't just want to say alloc init,
because I need to say this managed object is based
| | 04:57 | on a particular entity that we've described in
our model, the Course entity that I just wrote.
| | 05:03 | And to do this, Core Data requires not just
the entity name but another piece of information.
| | 05:09 | So here's the classic most convenient way to
create a managed object from a named entity.
| | 05:15 | I am actually going to use the NSEntityDescription class,
which has a method called insertNewObjectForEntityForName:
| | 05:25 | inManagedObjectContext, so let me just
enter that one in and split it across a couple
| | 05:31 | of different lines so I can
talk about it a bit easier here.
| | 05:35 | There are other ways to create a managed object.
This is the simplest.
| | 05:39 | Now it might look a little strange because
to create this NSManagedObject we are using
| | 05:44 | a factory method but not of the NSManagedObject
class, but a different class, NSEntityDescription.
| | 05:49 | And what we are saying is make an NSManagedObject
based on this particular entity, which I can
| | 05:55 | name with a string here, call it course, and
we need this extra piece of information, because
| | 06:00 | as soon as I create this managed object,
and this is true for every managed object, it
| | 06:05 | needs to be in a container, a named area
that's called a context or a ManagedObjectContext.
| | 06:13 | We'll talk more about what this container
exactly is in just a minute. For now, let's just do it.
| | 06:19 | I have a property of this class called
managedObjectContext, and that's what I need here.
| | 06:24 | So this method that we are using is saying
make an NSManagedObject based on this entity
| | 06:29 | and make it in this container, this
ManagedObjectContext, and return me a reference to it.
| | 06:35 | We now have a managed object.
And I want to set the values of this.
| | 06:39 | Well, in the entity I just made, I had
things like title and author and release date, so
| | 06:44 | it would be nice if I could say something
like myMO.title equals, but I don't have that.
| | 06:51 | Unfortunately, that won't work yet because
I created this as a NSManagedObject
| | 06:57 | reference, and that's a generic class, and
it doesn't know about custom properties.
| | 07:02 | We'll see a little layer how I could get some
custom properties and have .title and .author
| | 07:07 | and .subject or whatever
else I had directly accessible.
| | 07:11 | But in the meantime, what I have to do is
use key-value coding to set those properties.
| | 07:16 | It's a little clunkier, but it will work.
| | 07:18 | So I am using the SetValue: forKey method, giving it a value
such as Core Data for the key, and this is a string, title.
| | 07:36 | And this refers to the name of the
attribute that I defined in the Entity.
| | 07:43 | Next, I'll set the value for the author
attribute, and finally, I'll set a value for the release
| | 07:53 | date attribute, here this is going to
be an NSDate, and right now that's it.
| | 08:04 | I am going to run this, Run.
| | 08:06 | I am going to go ahead and when this opens,
I am going to click the button and click the
| | 08:11 | button and click the button.
| | 08:13 | Well, I shouldn't really expect to see very
much as what we are doing, we are doing completely
| | 08:19 | silently, there's no alerts, no messages,
no log messages, and for now that's okay.
| | 08:24 | So here's the question, is
this object being created?
| | 08:28 | Yes, in fact, every time I click
this button I'm creating another one.
| | 08:32 | Now is it being saved?
| | 08:33 | Well, no, not yet, but
they will be in just a second.
| | 08:39 | So I have created several objects here, and
I'm now going to quit out of this app.
| | 08:44 | I can either use the menu, or
I can just hit Command+Q.
| | 08:50 | But at that particular moment, when I
terminated the application those managed objects were
| | 08:55 | saved by Core Data.
Well, why and how and prove it.
| | 09:01 | See, there is nothing weird or
automatic going on, you see?
| | 09:04 | In a default Cocoa Application with Core Data
when you terminate an app, it's going to call
| | 09:09 | the usual application terminate event here,
we have got applicationShouldTerminate, so
| | 09:15 | I am going to jump to that method, here we go,
and there are some code that's been provided
| | 09:19 | for us to tell Core Data to save.
It's actually this line here.
| | 09:26 | Now we'll explore this code in depth in
just a bit. There is a bunch of code here, most
| | 09:30 | of it is actually just dealing with
generating alert messages if there's a problem.
| | 09:35 | But I do see the actual word save here,
and here is where we are saving those objects.
| | 09:39 | And Apple have put this code in the applicationShouldTerminate
method because that's the way they've been
| | 09:44 | moving towards over the last few versions of
OS X, that if you just quit from an application,
| | 09:49 | it won't prompt you to save, it
will just automatically save your work.
| | 09:53 | Now we will be able to also choose to
save anywhere else we want, anytime we want.
| | 09:58 | We don't have to wait until the application
terminates, it's just already here in the template.
| | 10:03 | But first, how can I prove this work?
| | 10:05 | Let me show you where Core Data is
going to save data for a Cocoa App.
| | 10:09 | I am going to open up a Finder window,
actually here what I need to do is go to my Go menu
| | 10:14 | and say Go to folder, go to my home folder
Library, and I am going to jump into Application
| | 10:24 | Support, and here's where it was important
that I looked at my com.company as the name
| | 10:30 | here because this is the Application Support
folder that's been created for this application,
| | 10:37 | com.company.project name, which was SaveDemo.
| | 10:40 | If I open that up, I can see I
have got this SaveDemo.storedata file.
| | 10:46 | This is what's called the store
file or the persistent store file.
| | 10:50 | Our managed objects have been serialized,
encoded, archived, whatever term you want
| | 10:54 | to use by Core Data into this file.
And I can prove that.
| | 11:00 | You see, in Core Data, the internal structure
of this store file can take several different
| | 11:05 | forms, you can tell Core Data what
format you want it to use when saving.
| | 11:09 | Although, until you have a reason to change it
you just leave it alone, leave it at the default.
| | 11:14 | And in a default Cocoa Application using Core Data,
the format of this is actually XML, and that's readable.
| | 11:20 | So I'm going to Ctrl-click or right-click
on this and tell it to open up with--nothing
| | 11:26 | is showing up right now, so I'm
going to select Xcode and click Open.
| | 11:32 | And this is our persistent
store file, or what's inside it.
| | 11:35 | That doesn't look completely obvious here. We've
got some strange names like NSPersistentFramework
| | 11:41 | and lots of keys, but if I scroll down,
this looks like the kind of thing I'd expect to see.
| | 11:47 | We've got an XML format here of an
object of type Course with three attributes inside it
| | 11:53 | for title, release date, and author, and this has all
been taken care of by Core Data simply because
| | 11:59 | we described those entities.
| | 12:00 | Now I am not going to change anything about this.
I just wanted to prove that it was actually
| | 12:05 | happening that this information was being
saved into our file system because I'd click
| | 12:10 | that button several times.
| | 12:12 | Now for an iOS application, the default
storage format is actually using SQLite, but it is
| | 12:18 | still storing a data file to the iOS file
system just like this version is.
| | 12:24 | It's just not as easy to navigate
and actually demonstrate that.
| | 12:27 | Now of course, these values can be read
back in, but we are not actually worrying about
| | 12:32 | that for now. That's going
to come in the next phase.
| | 12:35 | What we need to worry about and understand
a little better is the object that we had
| | 12:40 | to use to make this work,
the ManagedObjectContext.
| | Collapse this transcript |
| Understanding the managed object context| 00:01 | The Managed Object Context is
a vital object in Core Data.
| | 00:05 | It is the beating heart of Core Data.
| | 00:08 | The Managed Object Context
sits in the middle of everything.
| | 00:11 | It's not necessarily the most complex object, but it
is the most connected, the one that drives everything.
| | 00:16 | It's the conductor conducting the
orchestra, it's the engine of Core Data.
| | 00:20 | Now earlier, I said that one of the benefits
of Core Data is that we don't have to traverse
| | 00:26 | through all our objects, for the ones we want to
save or at least calling save on every single one.
| | 00:32 | What we do is we take our entire group of
related managed objects or object graph and say save.
| | 00:39 | Well, how we group them is the Managed Object
Context, it's a specific type NSManagedObjectContext
| | 00:46 | that you have available
when you link to Core Data.
| | 00:49 | We put all our managed objects inside
this context, and we call save on it.
| | 00:56 | Because in Core Data, an individual managed object
just by itself doesn't have a save method.
| | 01:01 | A Managed Object Context does, and
that's why when we create a new individual
| | 01:07 | managed object, we always
create it inside a context.
| | 01:10 | If it's not inside a context, it can't do
anything, because it's the context this container
| | 01:16 | that we perform the most
fundamental operations on in Core Data.
| | 01:20 | When we save, we do it on the context, when
we fetch data out of the store, we will fetch
| | 01:25 | directly into a Managed Object Context.
| | 01:29 | If we want to work with Undo and Redo, we
don't call Undo and Redo on individual objects,
| | 01:34 | we call Undo or Redo on the
entire Managed Object Context.
| | 01:38 | And we do all this so that Core Data can keep
track of this object graph and all the multiple
| | 01:44 | objects in it, and we don't have to.
| | 01:46 | Now sometimes, you'll hear the analogy of a managed
object context is being like a scratch pad or a workbench.
| | 01:55 | And in some ways that almost trivializes
what this container can do, it's worthwhile to
| | 01:59 | remember those words that a
Managed Object Context is a work area.
| | 02:04 | We can bring objects into it, delete them
from it, we can manipulate them, and it will
| | 02:09 | keep track of all the changes to these
objects, but nothing is saved automatically.
| | 02:15 | It's only when we tell the context to save
that it will take these objects and push them
| | 02:19 | out to the store file.
| | 02:21 | So while creating the data model, describing
our entities is what we need to do up front
| | 02:26 | using that Data Model Editor we've seen.
| | 02:28 | When we actually start writing the code in
our app, it's the Managed Object Context that
| | 02:33 | will be the main part of Core Data, we'll
be writing code to, we'll be interacting with
| | 02:37 | to accomplish all the things we want.
| | 02:39 | So how do we get a managed
object context? How do we make it?
| | 02:43 | Well, as you see in a sample project, when
you select that Core Data check box, the code
| | 02:47 | to create one is written for you.
| | 02:49 | And most of the code that's added to a Core
Data template is to actually correctly create
| | 02:54 | this Managed Object Context.
| | 02:56 | And it's not that the code is hugely complex,
it's just that we need three things in place here.
| | 03:02 | First, we need an object to represent the
data model that we've written, and we get
| | 03:07 | that from our data model file, our Entities
that we've described, so there will be a little
| | 03:12 | code in our project that will turn those
entities into an object called a Managed Object Model.
| | 03:19 | And second, the Managed Object Context will
need to know where it's supposed to load and
| | 03:23 | save from, literally, where is the location
of that store file, the persistent store?
| | 03:28 | Well, in Core Data, there's actually another
object that handles all the details of that
| | 03:34 | actual communication, that object is called
a Persistent Store Coordinator, and it's that
| | 03:40 | that will take care of the actual store file,
whether that's XML or SQLite or one of the other options.
| | 03:47 | As a developer, we don't really care
about the Persistent Store Coordinator.
| | 03:51 | We just let it do what it needs to do.
| | 03:53 | It needs to be there, it's part of the
picture, and it needs to now about things like the
| | 03:58 | Managed Object Model, but it's not very
interesting, won't require much in the way of customizing
| | 04:03 | and most of the time won't
require us to do anything at all.
| | 04:07 | But with these two pieces in place, the Managed
Object Model and the Persistent Store Coordinator,
| | 04:13 | we can create the Managed Object Context.
| | 04:16 | And it's only now that we have the engine
up and running and everything revolves around
| | 04:20 | the Managed Object Context.
| | 04:23 | It talks to the Persistent Store Coordinator,
the Persistent Store Coordinator knows about
| | 04:28 | our data model, and it
knows where the store file is.
| | 04:31 | And we drive everything from the context.
| | 04:34 | And in a sample project, whether iOS or Cocoa, if
you've checked that Core Data check box, it's all done.
| | 04:41 | The extra code is there mainly
to set these three things up.
| | 04:44 | So let's take a look at it.
| | 04:46 | I've opened up a project in Xcode, the Cocoa
application I created a little earlier, doesn't
| | 04:51 | really matter what you'd be looking at,
whether this was a Cocoa app or an iOS app, I just
| | 04:56 | want one that have that
Core Data check box checked.
| | 04:59 | If I jump into the AppDelegate header file,
I'm going to find three properties here for
| | 05:03 | the three elements I just described, the ManagedObjectModel,
the PersistentStoreCoordinator and the ManagedObjectContext.
| | 05:11 | Because this ManagedObjectContext is almost
always wanted across the entire application,
| | 05:17 | the AppDelegate is a pretty good place for
it to be, although it can be useful to pass
| | 05:22 | a reference to this around into other view
controllers, particularly in iOS, just so it's
| | 05:27 | always convenient to call
it from wherever you are.
| | 05:30 | What we'll find, if I jump across to the
implementation for this is there'll be methods to do some
| | 05:36 | lazy instantiation for these.
| | 05:39 | So while up at the top of this they're
synthesized, if I use my jump bar, I'll be able to jump
| | 05:44 | and find methods for all of them.
| | 05:46 | First, I'll jump to the
method for the managedObjectModel.
| | 05:50 | A managedObjectModel is our in-application
representation of our data model file.
| | 05:58 | So what's happening here is it's
just first asking, well, do I exist?
| | 06:02 | If I already exist, just return the object.
| | 06:05 | If not, the lines 37 and 38 are going out
to our project resources and loading in a
| | 06:11 | file with a project name and extension MOMD.
| | 06:15 | That's coming from our xcdatamodel file,
that's converted into an MOMD file during the build
| | 06:20 | process, and this is all that does.
| | 06:23 | Just load that file and convert it into a code
representation of our data model, our entities.
| | 06:28 | Next, after that, we have the second
element, the persistentStoreCoordinator.
| | 06:33 | This has a bit more code.
| | 06:35 | Because first off, it's looking for a file location,
it'll actually be generating a directory if necessary.
| | 06:41 | Now this code would look a little
different in the iOS versus Cocoa templates because
| | 06:46 | the way of getting to the directories are
slightly different, and also, if you scan through
| | 06:50 | this, you'll find some
information about the store type.
| | 06:53 | Again, in a Cocoa application it's defaulting to the
XML format, whereas in iOS, it will be using SQLite.
| | 07:00 | But you rarely need to add this code yourself,
even if you have to add it to an existing
| | 07:05 | project, you're probably better off copying
and pasting most of it from a new project,
| | 07:10 | it's fairly standard stuff.
| | 07:12 | And finally, we have the
managedObjectContext creation method.
| | 07:18 | Most of the code in here is actually just
checking to make sure that the Persistent
| | 07:21 | Store Coordinator already exists, and if he
doesn't, there's a problem, otherwise we're
| | 07:26 | doing an alloc init on
that and returning that value.
| | 07:30 | So the creation of the Managed Object
Context is actually quite simple, but we need the
| | 07:35 | other things in place to make it.
| | 07:38 | And this, in fact, is the majority of code
that's added to a Core Data template, everything
| | 07:42 | else revolves around this.
| | 07:44 | This is the code you would need in your
own projects if you couldn't check that check
| | 07:48 | box when you originally made it.
| | 07:50 | So as we now have this Managed Object Context,
next, let's see how we can use it to perform
| | 07:56 | a manual save at the time of our own choosing.
| | Collapse this transcript |
| Saving the managed object context| 00:00 | So I'm still in this simple Cocoa Application
where I'm creating a new managed object based
| | 00:05 | on an entity I've described and
just setting a few values of it.
| | 00:09 | I know this is being saved when the
application terminates, but let's say I want it to save
| | 00:13 | immediately as soon as this object is
created, rather than waiting for someone to quit.
| | 00:17 | And all I need to do that is call the
save method of the Managed Object Context.
| | 00:24 | To prepare for that, I need to create an
NSError reference here. I'll just call this error.
| | 00:30 | Though it can be nil, I actually don't need
an NSError object, I just need something to
| | 00:35 | hold one and the pass that into the save method.
| | 00:39 | Now there is a method in App Delegate itself
that returns the Managed Object Context, so
| | 00:46 | that's the easiest way to call it and then say
save and passing in the address of that error pointer.
| | 00:55 | This would do it here; however, this method,
call, the call to save will return a Bool,
| | 01:00 | so it's going to be yes or no, yes
if this is successful, no if it isn't.
| | 01:04 | So it's very common to actually wrap this
in an if statement. Let's quickly do that.
| | 01:10 | So I'll just grab that here, and we want to
test for the negative if there is a problem.
| | 01:21 | So I'm calling the save method, I am negating
it, saying if there is a problem if it doesn't
| | 01:26 | return yes, I'm going to
pop up this NSLog. That's it.
| | 01:30 | And that's what I mean by saying once you do
the setup, once you've done the data model,
| | 01:34 | once you have got your Managed Object Context
up and running, this part is easy and saving
| | 01:39 | this Managed Object Context, it's no more
complicated if you have 1,000 objects.
| | 01:44 | In fact, anything else we might want to do
right now becomes more to do with the user
| | 01:49 | interface than it is with Core Data.
| | 01:51 | So, for example, if I look at the sample code
in this Cocoa App, the code that was provided
| | 01:57 | by selecting that check box, I am going to
drop into the applicationShouldTerminate section
| | 02:03 | where we're doing the save here, and that's
actually the same call to save.
| | 02:08 | Most of the code after it is really just presenting
model alert information in the case that there was
| | 02:13 | an error, it's really
all to do with the UI here.
| | 02:16 | However, there are a couple of things before
the attempt to save that are worth talking about.
| | 02:22 | As soon as someone requests to terminate
the application we are first asking, well, does
| | 02:26 | that Manage Object Context even exist?
| | 02:30 | Remember that we do lazy instantiation, so
if you've never tried to do anything with
| | 02:34 | Core Data there might not be a Managed
Object Context object to work with, so if that's
| | 02:39 | the case, we'll just say
NSTerminateNow, go ahead and quit, it's fine.
| | 02:44 | Now, next on line 165, where we're asking
the managedObjectContext to commitEditing and
| | 02:52 | checking the result of that.
| | 02:53 | Now this is really only for the Cocoa desktop,
folks. This is not something you would see in iOS.
| | 03:00 | commitEditing is really part of NSEditor, which is
associated with controllers and user interface elements.
| | 03:07 | What this means here is if you're using Cocoa
Bindings to bind your managed objects directly
| | 03:13 | to user interface elements--now we are not
doing that yet, but we might--and say the user
| | 03:18 | is right now editing a text field that's bound
to one of these objects, we want to make sure
| | 03:23 | that those changes are saved.
| | 03:25 | So it's asking if that's happening it's
attempting to commit right now, and if there's a problem
| | 03:29 | committing those changes, we are actually
going to cancel the attempt to terminate and
| | 03:35 | keep the application running.
Again, still all about the user interface here.
| | 03:40 | Finally, right before the save, we are asking
the managedObjectContext, do you have any changes?
| | 03:46 | Remember that it's keeping track of changes
to all the objects inside it, and all we are
| | 03:51 | saying here is if the call to hasChanges
returns no, then there is nothing to save, we don't
| | 03:56 | need to save, we can
just go ahead and terminate.
| | 03:59 | Right now we can go ahead
and quit the application.
| | 04:02 | And asking if the managedObjectContext
hasChanges is useful in the iOS 2 just so you're not
| | 04:08 | attempting to save if you don't
need to, and that's pretty much it.
| | 04:12 | Now if I go back up and looking at the area
where we're creating and setting the properties
| | 04:18 | of this managed object, well, I did promise
that I'd show you a better way than key value
| | 04:23 | coding that we are having to use here.
| | 04:25 | So next, we are going to see how to create
better, more specific managed objects and
| | 04:29 | also how to add custom behavior to them.
| | Collapse this transcript |
| Creating custom managed object subclasses| 00:00 | So I'm continuing to use this straightforward
example that I have where this Cocoa project
| | 00:05 | has a Course entity with author, releaseDate,
and title, and then in my code I've got a method
| | 00:12 | that instantiates a managed object based on
that entity, seta its properties, and saves it.
| | 00:18 | But I promised there was a better way to do
this, and that's by creating a custom class
| | 00:23 | for this managed object instead of
using the generic NSManagedObject pointer.
| | 00:27 | Now these doesn't mean that for the same
idea we need two things, an entity called course
| | 00:33 | in our data model, and also a custom
Objective-C class also called course.
| | 00:39 | The names don't have to
match, but they usually would.
| | 00:41 | The entity is for Core Data to describe how
this managed object is going to be saved, and
| | 00:47 | the custom class will be for our app to
describe our custom behavior for this managed object.
| | 00:52 | And it's very, very common to have both
an entity and a matching custom class.
| | 00:57 | Now if you're wondering do I
need to create custom classes?
| | 01:00 | No, you don't need to.
| | 01:02 | If you have an entity that is just a data
structure, you don't need a custom class for it.
| | 01:07 | You can do it just as we've done already.
This method will always work.
| | 01:12 | But if you want to provide custom behavior for
your managed objects, yes, you need a custom class.
| | 01:18 | If you want to use named properties instead
of key-value coding, yes, you need a custom
| | 01:23 | class, and it's very easy to do. Here's how.
| | 01:26 | Well, I could just go into Xcode and from
the File menu add a New > File and do this
| | 01:32 | myself, but there's a better way, an easier way.
| | 01:35 | I'm going to jump into the data model, and with
that selected I'm going to go to the Editor part
| | 01:39 | of the menu and come down to this
option Create NSManagedObject Subclass.
| | 01:46 | Generate some Objective-C code
based on this entity. We'll click that.
| | 01:53 | Now if there was more than one entity in my
data model I'd get a dialog box here asking
| | 01:57 | which entities I was interested
in, but here we've only got one.
| | 02:01 | So my next screen is just a save screen,
where do you want to save your new class?
| | 02:05 | This will just be a regular
Objective-C class here.
| | 02:08 | There is an option down here for Use
scalar properties for primitive data types.
| | 02:13 | By default, primitive types defined in your
entity will be wrapped in an NSNumber for
| | 02:18 | your managed object, and usually that's fine.
| | 02:21 | So unless you have a problem
with that, I'd leave this uncheck.
| | 02:24 | I'll just go ahead and
click Create, and we are done.
| | 02:28 | We have Course.h and Course.m in our project.
| | 02:34 | Looking at the header file, it's
pretty much what you would expect.
| | 02:37 | We've got properties for the NSString for
author and title and an NSDate for releaseDate.
| | 02:43 | The only difference here from a standard object
is that we are inheriting from NSManagedObject
| | 02:50 | rather than just NSObject, and that's important.
This wouldn't work properly otherwise.
| | 02:55 | Now if I jump over into the implementation,
what I can see here is that the properties are using
| | 03:00 | the at sign dynamic keyword rather than
synthesize, and this is the default for managed objects.
| | 03:07 | It means the property assessors are dynamically
generated at runtime, not synthesized at compile time.
| | 03:14 | Again, that is the default for
managed objects. Just leave it at that.
| | 03:18 | There is one more thing that's been changed.
| | 03:20 | If I jump back into the data model, what's
happened is this entity--I'm going to open
| | 03:25 | up the Utilities panel here make sure the
entity itself is selected--and over here we
| | 03:31 | have an option called Class.
| | 03:34 | The Entity in the data model knows
that its associated Class is Course.
| | 03:39 | Now a moment ago--and I can do this just by
deleting from class--what the default would
| | 03:44 | have been was NSManagedObjects.
| | 03:46 | So unless we say differently, that's what
it will be, but we have the custom class.
| | 03:51 | So what's the difference in code?
| | 03:53 | But what I can do is that in my
AppDelegate now where I'm creating this object what I
| | 03:58 | can do is have an actual custom
course pointer rather than NSManagedObject.
| | 04:03 | Well, first I need to jump up to the top of
this file and do an import--of the course header
| | 04:13 | file--and jump back into
where I'm creating that object.
| | 04:18 | So instead of NSManagedObject, I'm going
to create a pointer to a course object.
| | 04:23 | Now because I'm still using this same
method to create the managed object,
| | 04:29 | the insertNewObjectForEntityForName: inManagedObjectContext,
| | 04:33 | that will always return a
base type of NSManagedObject.
| | 04:36 | So I do need to cast that
returned object into a course.
| | 04:41 | So I'll just put a cast in front of it.
Cast this to a course pointer.
| | 04:47 | Now so what's the difference?
| | 04:48 | Well, instead of having to use key-value
coding, I get named properties.
| | 04:53 | I can type myMO.title, Core Data, author,
or of course the sent versions of that, so
| | 05:08 | myMO setReleaseDate.
I don't need the KVC versions.
| | 05:18 | Of course, one of the benefits here is this
is giving me compile time, checking on these
| | 05:23 | properties, and that's
something I wouldn't get with KVC.
| | 05:26 | So it's much easier with key-value coding
to make a mistake and mistype something.
| | 05:31 | That's nice to have, but what I can also
do is add behavior to this custom class.
| | 05:36 | As soon as it's generated, we can do whatever
we want with it, so I can add a little method
| | 05:41 | here, and in the implementation just provide
something simple so we can prove that this works.
| | 05:51 | I jump back into my AppDelegate, even though I'm
creating this as a ManagedObject. I can now call a
| | 05:57 | method of that myMO SimpleMethod.
To prove these work, just go ahead and run it.
| | 06:05 | Every time I click the button we should
be creating an object of that custom class.
| | 06:10 | It's still a managed object,
so Core Data is still handling.
| | 06:14 | Just making sure I can see that
information in my results here.
| | 06:18 | There we go, Custom behavior,
Custom behavior, Custom behavior.
| | 06:21 | So it's a custom class allowing us to do
everything we do normally but which is still a managed
| | 06:26 | object being saved and managed by Core Data.
| | 06:29 | I'm going to click out of that
and back into the application.
| | 06:31 | Now one of the things I'm asked is: is
there a way to automatically update these class
| | 06:36 | files if you make at change to the entity?
No, it's pretty much one way.
| | 06:41 | You can always regenerate those class
files again from the entity, but it's not going
| | 06:46 | to pay any attention to changes you've
added to the class like Custom behavior.
| | 06:50 | Now it won't just overwrite them.
| | 06:51 | It'll warn you if you try and do this, but
just be careful and be aware, that is your
| | 06:55 | responsibility to handle copy any custom data
across if you want to regenerate these class files.
| | 07:02 | Or just manually updating that class later on to
match any changes that you might make to the entity.
| | 07:09 | Because there is no magical connection going
on under the hood you're seeing everything here.
| | 07:14 | All you have here is the .h the .m and the
data model that in the Utilities section is
| | 07:21 | actually pointing over to my custom class.
That's it. That's all there is.
| | 07:26 | The only official link between the two
is this class name and the data model.
| | 07:31 | What's probably becoming apparent is if you are
going to use this feature--and it's very welcome to have--
| | 07:36 | obviously, the entity comes first, and in a
production project you'll want to get your
| | 07:41 | entities as well defined as possible before
you generate any custom classes from them,
| | 07:46 | because there's this no point to generating
custom subclasses from an entity if you expect
| | 07:52 | that 5 minutes later you're going
to change the definition of the entity.
| | 07:55 | If you've already written the classes, you can
of course just link them later on duplicating
| | 08:00 | an entity and changing the
class name of that entity yourself.
| | Collapse this transcript |
|
|
4. Fetching in Core DataCreating and using a fetch request| 00:01 | We've see how to model some entities, we've
seen how to create managed objects, placing
| | 00:05 | them in a managed object
context and using that to save them.
| | 00:09 | But being able to create and save objects
isn't much use if you can't get them back.
| | 00:13 | So let's see the third essential piece of
the puzzle, how to retrieve--or as we say in
| | 00:17 | Core Data--how to fetch our objects.
| | 00:21 | Now everything we do will still revolve around the
managed object context, the beating heart of Core Data.
| | 00:26 | But to fetch you must
create a fetch request object.
| | 00:31 | The fetch request describes what you want.
| | 00:34 | You don't fetch everything, and it's most
basic a fetch request will say that you're
| | 00:38 | fetching all the objects of a particular entity.
| | 00:42 | Fetch all courses or fetch all
books or fetch all publishers.
| | 00:46 | Now you can get more specific ordering or
sorting the results, so fetch all books sorted
| | 00:51 | in order of publication date
or only fetch specific things.
| | 00:55 | Just fetch all books with the word apple in
the title, but you'll always need the entity.
| | 01:01 | And to create a fetch request, you really
only need two things, the name of the entity, and
| | 01:06 | the name of the managed object context.
| | 01:09 | Because just as it's the managed object
context that performs a save, it will be the managed
| | 01:13 | object context that will execute our fetch request
and get the objects out of the store if necessary.
| | 01:21 | So let's go ahead and do this.
| | 01:23 | So I'm in Xcode. Now, if you've watched any
of my other courses you know that I really
| | 01:27 | like to do demos from scratch where possible.
| | 01:30 | Here that would be a little long, so I do
have a project that I'm going to start from.
| | 01:34 | Now if you don't have access to the exercise
files, or even if you do, let me explain what I've done.
| | 01:39 | I've made a Cocoa project with Core Data.
| | 01:42 | In the data model, I've created one entity
called Course with author, releaseDate, and title.
| | 01:49 | And I've generated a custom class for that
course, so I can have named properties, but
| | 01:54 | there's no custom behavior in it.
| | 01:55 | And if I jump into the main window for this,
I have two buttons, and only one of them has
| | 02:02 | any code behind it.
| | 02:04 | The Create Managed Objects button is calling a
method in my appDelegate called createObjects,
| | 02:13 | and all this method is doing is seeing the code that
we've seen before to create five different course objects.
| | 02:20 | I need to create these objects just
so we have something we can fetch.
| | 02:24 | So if I scroll down, there's just the
details for a few different objects here, and then
| | 02:28 | we're saving them in our Managed Object Context.
| | 02:31 | That's it. I don't have any other additions,
no other user interface elements, everything
| | 02:36 | else is the standard Core Data
Cocoa application. So let's fetch.
| | 02:41 | On my window I have a button, French Managed
Objects, and this is actually calling a method,
| | 02:46 | it's already hooked up in the background
called fetchObjects, but there's nothing there yet.
| | 02:51 | So everything we're going to do is in here.
| | 02:54 | One of the nice things about this is we
don't even need to type very much, because Apple
| | 02:58 | have quite kindly provided us
some Code Snippets we can use.
| | 03:02 | I'm going to just give myself a bit more screen
real estate here and open the Utilities panel.
| | 03:08 | Towards the bottom here, I'm going to
click the second icon, the curly braces to give
| | 03:12 | me my Code Snippet Library.
| | 03:13 | And because there's quite a few things here,
down at the bottom I'm going to filter on
| | 03:17 | the words Core Data.
And the first three are kind of interesting.
| | 03:22 | I've the Core Data Basic Fetch, Core Data
Fetch with a Predicate, Core Data Fetch with
| | 03:27 | the Sorting, we're using
the first one right now.
| | 03:29 | So all I'm going to do is grab this and
drag it over into my fetchObjects method.
| | 03:35 | I've got a few exclamation marks here
just because I need to fill in some details.
| | 03:40 | On this line 194, we're
creating a new fetchRequest object.
| | 03:44 | I said that it needed two pieces of information,
watch the Entity name, and watch the ManagedObjectContext.
| | 03:50 | So here we've got a couple of placeholders.
What is the entity name we're interested in?
| | 03:54 | We're interested in Course.
| | 03:57 | Hitting tab, it will jump me to the second
part. It needs the ManagedObjectContext.
| | 04:01 | Well, I can get this by calling the
ManagedObjectContext method of self.
| | 04:09 | That will do there, so we're creating the
fetchRequest, defining the entity for it, and
| | 04:15 | then we're setting that entity as a
property of the fetchRequest, simple as that.
| | 04:20 | Down here we're just about to execute it.
| | 04:22 | I execute the fetchRequest on the ManagedObjectContext,
so this is, again, just a call to self managedObjectContext.
| | 04:32 | We execute the fetchRequest. If there's
any error, we're going to have a problem here.
| | 04:36 | So we execute the fetchRequest in the same
way that we execute a save, and when we do
| | 04:40 | a fetch we get an array back.
| | 04:43 | The Error handling code that we've got starting
here on line 200 is asking if this fetchedObjects
| | 04:48 | array is equal to nil.
| | 04:50 | Now it's perfectly acceptable that we might
retrieve an array with zero objects in it.
| | 04:55 | Maybe there aren't any saved entities,
but what we don't expect is a nil array.
| | 05:00 | So if it's coming back with nil after
that execute, we've got a problem.
| | 05:09 | And I'll just put in a basic message here.
| | 05:11 | Now we have an issue here on line 204, and
that there is a release to the fetchRequest object.
| | 05:16 | We're using arc so we can't
do that, so I'll just delete that line.
| | 05:21 | And because the indentation isn't very
nice, I'm just going to select my code here and
| | 05:25 | from Editor come down
and Re-Indent, there we go.
| | 05:30 | So this will create the fetchRequest, set the
entity, and then execute it on the managed object context.
| | 05:36 | Well, now what? Well, we should
have an array of objects back.
| | 05:40 | I don't currently have a user interface element
to put them in, so I'm just going to log some
| | 05:44 | messages to prove this fetch worked.
| | 05:47 | I'm going to do a for statement, but I'll
use the Objective C fast enumeration here.
| | 05:52 | I'm going to go through every course
that should be returned in this array.
| | 05:57 | So Course, and we'll call it c in the
array, array is called fetchedObjects.
| | 06:03 | I'll just write out a simple message and just use a
couple of the properties for the purposes of time here.
| | 06:15 | Okay, let's save that, and I'm
going to go ahead and run this code.
| | 06:24 | Now if I click the button to fetch the
managed objects, I wouldn't expect to see any message
| | 06:28 | because there shouldn't be anything.
| | 06:29 | But neither would I expect a problem. It's
perfectly acceptable for that code to go.
| | 06:35 | I want to go and fetch all these
courses, okay, there aren't any, that's fine,
| | 06:39 | but what I'm going to do is click the first
button which will go ahead and create five of them.
| | 06:44 | We click that, and then I'll click the
second button again, and what I should get, other
| | 06:50 | messages down here iterating through those Course
objects and just writing out the title and the author.
| | 06:58 | Now I'm going to just clear out of that, jump
back to the application, and press that button,
| | 07:03 | say three times, and notice that if I do
press that button, that creates them and press the
| | 07:08 | Fetch one again, we're going to see them all
listed again, in this case we'll have 15 of them.
| | 07:13 | Because even though they might contain the
same values, I'm executing code to instantiate
| | 07:17 | five managed objects whenever I click that top
button, and it will keep adding them to the store.
| | 07:22 | So even if I clear this out, and now go back and do
a basic Fetch, we should expect to see 15 of them.
| | 07:29 | Now one of the things you'll find is that
with a basic fetch you will get all the objects
| | 07:34 | on this entity type, but they're not
guaranteed to be in any particular order.
| | 07:39 | So next let's see how to fetch them in order.
| | Collapse this transcript |
| Ordering with sort descriptors| 00:00 | So when I execute this fetch, I have all these
entities being brought back but in a non-specific
| | 00:06 | order, and to order these in some fashion we
use the same fetchRequest. It's exactly like this.
| | 00:11 | We just add to it.
| | 00:13 | We add a new very simple object called a sort
descriptor in that so all it does is describe
| | 00:19 | a sort, and to do that it just needs two pieces
of information, what attribute are we sorting
| | 00:24 | on title, author, releaseDate, and do I want
that ascending or descending? So let's do that.
| | 00:30 | After I've created the fetchRequest I'm going
to create a new instance of an NSSortDescriptor.
| | 00:41 | And the init I'm going to use
here is the initWithKey: ascending.
| | 00:46 | The key is just the name of the attribute
I want to sort on, which for my case I'll
| | 00:50 | do releaseDate, and ascending is NO,
meaning I'd like that descending.
| | 00:59 | Order by releaseDate
descending, the highest one first.
| | 01:01 | If I misspelled the attribute name like I
nearly did there, you'd find an error as soon
| | 01:06 | as you try to execute that fetchRequest,
but you wouldn't catch it until runtime.
| | 01:10 | Now we often want to sort by multiple
attributes, say first sort by author name ascending, and
| | 01:16 | then within that by releaseDate descending,
so we could do all the different authors and
| | 01:22 | their courses by the latest one first, and
all you do to do that is just create multiple
| | 01:27 | NSSortDescriptor objects the
same way I just did this one.
| | 01:31 | So you can have a second, a third, a fourth.
| | 01:34 | Now because the fetchRequest object knows
that we could be providing it multiple sort
| | 01:40 | descriptors, we have to give it an array of
sort descriptors, even if there's only one,
| | 01:44 | so I need to put this in an array.
Let's make an array now.
| | 01:51 | Use initWithObjects and give it the first
object, which is the sort I created on the
| | 01:56 | previous line. That's all I
need to do just finish that line.
| | 02:01 | If I had more sort descriptor objects, I'd
just put them in sequence before the nil.
| | 02:05 | And finally, I'm going to give this array
to the fetchRequest, so it's the fetchRequest that
| | 02:10 | I created up here, making sure I do
that before we actually execute it.
| | 02:18 | And we've got the setSortDescriptors method just
passing in the array SortDescriptors, Save and run.
| | 02:29 | I've already pressed the button to Create
these Managed Objects, they're already saved in
| | 02:33 | my store file, so when I fetch I should get them.
| | 02:35 | There are a few duplicates in there, but they should all
be sort of the same way. We'll take a little look here.
| | 02:42 | I'm getting them sort in releaseDate
descending so that the Core Data Course first and then
| | 02:46 | the iOS with MapKit and Core Location back
to Cocoa, C/C++, and Java was the oldest one,
| | 02:53 | so that's exactly how I'd expect it.
| | 02:56 | Just to prove this is working fine, we'll jump
through and get rid all these comments here,
| | 03:02 | and I'll sort it the other way around.
| | 03:04 | And all I'd need to do here is change the
ascending part of my SortDescriptor to YES and run
| | 03:16 | that again just to prove that it works, and we should
see everything the other way around, Java being first.
| | 03:23 | And that's pretty much it.
| | 03:26 | If you come from an SQL background, you can
pretty much imagine the Sort Descriptor object
| | 03:31 | as the equivalent of just the
order by part of a select statement.
| | 03:35 | We describe the sort, we add it to our
fetchRequest, and we execute the fetchRequest.
| | 03:40 | But what we're not doing is restricting any
of these results, we're just ordering them.
| | 03:45 | And you're probably already wondering, how
do we filter these down, how do we restrict
| | 03:49 | the entities that we're fetching?
And we'll see that next.
| | Collapse this transcript |
| Using predicates| 00:01 | So I have code here to create the fetchRequest, to
add a sort descriptor object to it and to execute it.
| | 00:07 | The next step is only fetching specific
entities instead of fetching all of them.
| | 00:12 | So only fetch courses within a certain
date range, only fetch employees with the last
| | 00:16 | name beginning with M, only fetch books
where the title includes the word Apple, or only
| | 00:22 | fetch accounts where the balance is negative.
| | 00:24 | Now to do this, we use Predicate Objects, and
a Predicate will be part of our fetchRequest.
| | 00:31 | We will write a Predicate and attach it to the
fetchRequest just as we created the SortDescriptor
| | 00:36 | object and attached that to the fetchRequest.
| | 00:40 | So I am using the NSPredicate class here,
and this is not unique to Core Data.
| | 00:45 | NSPredicate is part of the regular foundation framework,
actually as a SortDescriptor that we just used.
| | 00:51 | And a Predicate is how you
describe something that should be true.
| | 00:56 | You can think of a Predicate almost as an
object that just has the condition part of
| | 01:01 | an if statement or a while statement, well,
in fact it can be a lot more complex than
| | 01:05 | that, but what we're trying to
describe is a situation we want to be true.
| | 01:10 | For those of you with a SQL background,
the Predicate is almost like the where part
| | 01:14 | of your select statement.
| | 01:16 | So I am going to create one here using the
method of NSPredicate called predicateWithFormat.
| | 01:22 | And what it wants is an NSString.
| | 01:24 | It wants me to describe
what I think should be true.
| | 01:27 | Well, I am going to say that for now I just
want courses where Bill Weinman is the author.
| | 01:33 | And this is a simple example. We are going to
go through a few in the next few minutes here.
| | 01:38 | So I'll give it a string literal, and this
is what I am going to type, author == and
| | 01:44 | then in single quotes, Bill Weinman.
| | 01:48 | Because the larger string is in double quotes, I am
using single quotes around the string I want to match on.
| | 01:53 | Now it doesn't actually matter in a
Predicate string whether I have a double equals or a
| | 01:58 | single equals sign, but I like staying in
the habit of using double equals for quality.
| | 02:04 | So that's it, that's the predicate created.
Now I need to attach it to the fetchRequest.
| | 02:10 | So just fetchRequest, setPredicate, and the
predicate object we just made, save that,
| | 02:16 | go ahead and run it.
I will click Fetch Manager Objects.
| | 02:20 | Well, I have a few duplicate objects here
because I had clicked that first button a
| | 02:25 | few times, so I have got a bunch more objects
than I actually need, probably about 20 of them.
| | 02:30 | I don't have to clear them out, but I
would actually just like to get to the position
| | 02:34 | where I have only got one of each.
Here is the easiest way to do it for me.
| | 02:37 | I am just going to delete my
store file and let it recreate it.
| | 02:41 | So I will quit out of that, jump over to my
Finder, and then click my Go button and just
| | 02:47 | go directly to the folder under my home
folder called Library/Application Support/, and in
| | 02:53 | there I should find the entry for this app,
and this will be dependent on what my company
| | 02:59 | entry was for my project, but for me it's
com.myCompany.FetchDemo, and I am just going
| | 03:05 | to go ahead and delete the store file
because the code inside this Cocoa application will
| | 03:11 | actually recreate that
store if it doesn't find one.
| | 03:14 | So let's prove that, go ahead and run it again.
| | 03:17 | If I click Fetch, I should have nothing, but
if I click the first button I will just create
| | 03:21 | one set of object, and now when I
fetch, I get one, that's about right.
| | 03:27 | So let's make this predicate
a little more interesting.
| | 03:30 | Well, one thing to see is is it
case sensitive and absolutely it is.
| | 03:34 | So if I try and match on lower case, run
that again, click Fetch, we have nothing because
| | 03:41 | the actual value of the attribute
had an uppercase B and an uppercase W.
| | 03:45 | But what we can do is tell this predicate I
don't want this equality check to be case sensitive,
| | 03:51 | and here is how we do that.
| | 03:53 | After the double equals, I am just going
to use square brackets and put in the letter C,
| | 03:56 | save that and run it.
| | 04:00 | This time it doesn't matter what the
case is of that, it'll match anyway.
| | 04:05 | Couple of other examples we could do here.
Instead of having the full name, I could use
| | 04:11 | the word LIKE and then have the string but
have a wild card. I can use the asterisk for
| | 04:18 | a wildcard, which means any
amount of letters after the word bill.
| | 04:22 | Again, this is actually case sensitive so just
using LIKE and trying it this way should fetch
| | 04:28 | nothing, but if I added the C after the LIKE
and tried it again, we should fetch Bill's course.
| | 04:37 | Now C isn't the only other word we could use.
| | 04:40 | For example, if I type in V-E here--so I
want anything where it begins with the letters
| | 04:47 | V-E for an author--run that,
and fetch, I get nothing.
| | 04:52 | But I do have the author of Veronique
Brossier, the problem is is that E in Veronique has
| | 04:58 | the accent over it.
| | 04:59 | So another thing I could do here is add a D,
so make this both insensitive to case and
| | 05:05 | insensitive to diacritical marks,
accents and circumflex and things like that.
| | 05:11 | We run that again, click Fetch, and
we will get Veronique's course here.
| | 05:16 | Now when you are using LIKE, you can put the
wildcards either at the start or at the end
| | 05:22 | or anywhere in the middle.
| | 05:24 | So if I do this and put in V-I-D in the
middle of it, run that again, we will get David's
| | 05:30 | course because the V-I-D is somewhere
in the middle of that wildcard string.
| | 05:34 | If you knew that what you wanted was just
matching on, say a first name, instead of using
| | 05:38 | LIKE, you could say BEGINSWITH.
Then we don't need the wildcards.
| | 05:46 | It is still case sensitive.
I want to be careful there.
| | 05:49 | Again, we could make it case-insensitive
with the C in the square brackets, try that
| | 05:54 | again, and we get David Gassner's course.
| | 05:56 | Technically, you don't have to say BEGINSWITH
or LIKE all uppercase, that's just by convention,
| | 06:01 | the way that it should be done, so it's very
recognizable when you're scanning this string.
| | 06:07 | But of course, we don't just have to work with the
Predicate on author, we could do other things too.
| | 06:11 | Let's work with releaseDate.
| | 06:14 | Now that won't work
really well with a BEGINSWITH.
| | 06:16 | Let's say we wanted all the courses where the
releaseDate is before today's date, because
| | 06:22 | I know that one of them is after.
So I am going to say releaseDate less than.
| | 06:27 | Well, how do I say the current date?
| | 06:29 | We can have optional arguments in here,
and we do them like a format string.
| | 06:34 | So I am going to say releaseDate less than,
and then I will have percent sign, at sign
| | 06:38 | like a regular format string and after
the closing double quote, a comma, and we
| | 06:43 | will just create a new
NSDate for today, save that.
| | 06:50 | So give me all the courses where
releaseDate is less than today.
| | 06:55 | We run it, click Fetch, and what I will get
here is four courses, but not the Core Data
| | 07:01 | course, which because I haven't finished
recording it is certainly not going to be out today.
| | 07:06 | Not surprisingly, if I just turn that one
around, releaseDate greater than, I should
| | 07:12 | only expect one being retrieved,
which is the Core Data course.
| | 07:16 | And you can even combine these.
| | 07:19 | So for example, if I wanted author == David
Gassner and the releaseDate is less than today,
| | 07:29 | we can go ahead, run that, and get pretty
much what we would expect coming out of it.
| | 07:34 | So we have got equality, we've got greater
than, less than, greater than, equal to, less
| | 07:38 | than, or equal to, LIKE, BEGINSWITH,
we can also use the word CONTAINS.
| | 07:42 | We will see a few more Predicate strings as
we move through the rest of the course, but
| | 07:47 | rather than try and commit them all to memory,
do know that there is a Predicate programming
| | 07:53 | guide which you can get either from apple.com,
or it's available in your own documentation.
| | 07:59 | If I jump over here and do a quick search,
I will type in predicate programming guide.
| | 08:07 | I have multiple sets of documentation here,
so it might take just a second to come back
| | 08:11 | and jumping into one of these, we'll talk
about the basics of predicates and creating them.
| | 08:16 | Probably the most interesting thing
once you have gotten beyond the first few
| | 08:20 | is the Predicate Format String Syntax.
| | 08:23 | And that will kind of give you a summary of
what the operators you can use, equals and
| | 08:28 | double equals, equals to and greater than,
the words between, the ANDs and ORs are NOT,
| | 08:35 | BEGINSWITH, CONTAIN, ENDSWITH, LIKE,
MATCHES, if you want to use regular expressions.
| | 08:40 | So one of the things you could do is bookmark
the Predicate Programming Guide while you're
| | 08:44 | getting used to writing
your first few predicates.
| | 08:47 | And also, one last thing to point out with
this is know that one of the built-in code
| | 08:54 | snippets is a fetchRequest with a predicate.
| | 08:58 | So if I jump over into my Code Snippet
library, filter down on Core Data, we have got the
| | 09:04 | Core Data Basic Fetch, we have seen that,
Core Data Fetch with Sorting, we've done that,
| | 09:09 | there is also Core Data Fetch with a Predicate
that could be just dragged in and everything is there.
| | 09:14 | This is pretty much identical to what we have
actually just done, but it's a quick shortcut to get to this.
| | 09:19 | I'm going to go ahead and delete that
because all my code here works just fine.
| | 09:24 | But if you know that you have got to create
a few complex predicates, there is another
| | 09:28 | way to do it, where we can do it
visually, I will show that next.
| | Collapse this transcript |
| Creating fetch request templates| 00:00 | If you know that what you'll need to do in
your application is create a whole bunch of
| | 00:05 | fetchRequests together with predicates,
| | 00:08 | one option that you have is that instead of
writing this in code you can predefine these
| | 00:12 | in your data model file using the Model Editor and
save them as what are called Fetch Request templates.
| | 00:19 | Let me show you what I mean.
| | 00:20 | Say that I knew one thing I'd have to do
in my application is fetch all the courses
| | 00:26 | that have the word essential in them--so fetching in my
case, all the essential training courses or iOS essentials--
| | 00:33 | what I could do is go into this Data Model file.
| | 00:37 | Now under the Entities section, I can
see there is a Fetch Requests section.
| | 00:41 | Nothing is here right now,
but I can add something.
| | 00:44 | Either from the Editor menu we have got an
Add Fetch Request or from the button down
| | 00:49 | at the bottom that currently says Add Entity
but there is a little arrow there that I can
| | 00:53 | hold down and click and say Add Fetch Request.
| | 00:56 | I actually prefer using the Editor menu because
when you select another option from the button
| | 01:01 | it will actually change the
default behavior of the button.
| | 01:05 | So Editor > Add Fetch Request gives it the
default name of FetchRequest. I'm going to
| | 01:10 | change this to let's say EssentialCourses.
| | 01:14 | Now this name is what we're going to use in
code to grab this predefined Fetch Request.
| | 01:22 | And over on the right-hand side we have
the way to define it in this visual editor.
| | 01:27 | First, we have to provide it
what entity we are talking about.
| | 01:30 | Well, in my data model it's pretty simple.
| | 01:32 | So I only have the Course Entity, and
below that I have the predicate editor.
| | 01:38 | So where all of the following steps are true,
we click the little Plus sign here, and what
| | 01:42 | I do is have a dropdown list of my attributes,
again very simple data model here, but I could
| | 01:47 | say title, contains, or is,
is not, begins with, ends with.
| | 01:52 | Let's say contains Essential.
| | 01:55 | I could click the Plus button to add more
things to check on, but I don't need anything
| | 02:00 | other than that just a title.
Contains Essential is good enough.
| | 02:04 | This is a normal contains which
means it is actually case-sensitive.
| | 02:09 | One way I can change this is I can click
this button up on the top right section, which
| | 02:14 | takes us between this Visual Editor view and
just a view of what the predicate string would
| | 02:19 | be if you manually typed it yourself.
| | 02:21 | And you can change it here if you wanted to.
I could add the C in square brackets to make
| | 02:26 | it case insensitive. I am
just going to leave it as is.
| | 02:30 | So I'll save that. That's my Fetch Request
template predefined, so how do I get to it in code?
| | 02:36 | So I am jumping over into my AppDelegate.
| | 02:38 | I'm going to use the same place that I had
my previous code to compare and contrast here.
| | 02:45 | See what I won't need is all of this stuff up
here, and I won't need this predicate either,
| | 02:52 | I'll delete that in a moment.
| | 02:53 | But for now, what I'm going to do is
create the Fetch Request from that template.
| | 02:58 | The way that I get to that Fetch Request
template is by thinking about, well, where is it?
| | 03:04 | That is stored in our Data Model.
| | 03:07 | So I need to ask the model for it, and if
you remember that model is converted into
| | 03:12 | an object called a
managedObjectModel in our code.
| | 03:16 | So I can get to that by just calling self
managedObjectModel, not Context. Here I am
| | 03:23 | asking the data model, and there is an option
in there called fetchRequestTemplateForName.
| | 03:31 | That's the one that we want,
and I called it EssentialCourses.
| | 03:39 | That's going to grab the Fetch Request that
already has the predicate defined in it, already
| | 03:43 | has the entity defined in it.
| | 03:45 | So I don't need this code to deal with the entity,
and I don't need this code to deal with the Predicate.
| | 03:51 | I could still apply a Sort Descriptor if I
wanted to, again, we are still working with
| | 03:55 | the fetchRequest with the same name.
| | 03:58 | But I don't have to do that either. I'm going to leave
it there, and we execute it the same way we always did.
| | 04:03 | So if I go ahead and run this, we should now
just retrieve the courses with the word Essential
| | 04:09 | in them, and we do indeed, Java Essential Training,
C++ Essential Training, and Cocoa Essential Training.
| | 04:16 | And it makes our code a little bit more compact.
| | 04:20 | So predefining your Fetch Request is not
necessary, but it can be useful and helpful.
| | 04:26 | They can also be predefined with placeholders
for variables, and that we are not going do
| | 04:30 | that right now. We'll see it a
little later on in the course.
| | 04:33 | But what we do have is the basics of modeling,
of creating managed objects, of saving our
| | 04:40 | managedObjectContext, of fetching, of predicates,
and probably the most apparent missing piece
| | 04:45 | right now is tying these objects to some
kind of user interface in a meaningful fashion.
| | 04:50 | Now here is the thing.
| | 04:52 | While everything we've done so far is pretty much
generic across both Cocoa desktop and iOS development,
| | 04:58 | when we start connecting these objects to
a user interface, we are going to have to
| | 05:02 | approach things a little
differently for a couple of reasons.
| | 05:05 | One because, say on Cocoa Desktop development
we have Cocoa Bindings which isn't available
| | 05:10 | on iOS--at least not right now--and two, they just have
significantly different user interface elements and standards.
| | 05:17 | So in the next two sections what we are
going to do is take what we've done so far, all
| | 05:21 | the different stages, tying it all together
and tying it to a user interface, first working
| | 05:27 | through an iOS example and then with regular
Cocoa. We are going to see some other things.
| | 05:32 | You are probably wondering about right now,
how do you delete objects, how do you edit them?
| | 05:36 | Let's see that next.
| | Collapse this transcript |
|
|
5. Putting It Together: iOSCreating the project and the data model| 00:00 | Let's take these core ideas of modeling, saving,
fetching, and put them together inside a user interface.
| | 00:06 | Now first, we are going to do this with iOS.
| | 00:09 | We will create an app to show a list of courses,
showing these in a Table View grouped by author.
| | 00:14 | I can select each one to show the details of
that one, but we also have a view controller
| | 00:19 | where we can add a New Course
to either cancel or save that.
| | 00:26 | I can select an existing course, make a
quick change to some of those terms if I wanted to,
| | 00:32 | or from that initial list, I can delete one
and everything will be persisted to Core Data.
| | 00:42 | While this might not look so drastically
different from the example I showed with the timestamp,
| | 00:47 | we are going to do this
completely from the ground up.
| | 00:49 | We are not going to use the Master-Detail project.
We are going to do this from an empty iOS project.
| | 00:55 | So while we will let Xcode provide a
little bit of the standard Core Data code to set
| | 00:59 | up the Managed Object Context, everything
else, including the entire user interface
| | 01:04 | we will do ourselves.
| | 01:06 | Now it'll be a little
tough to do this in one go,
| | 01:08 | so I'll go through in several shorter pieces.
| | 01:11 | Part one, we start at the beginning,
create the project, write the data model, set up
| | 01:15 | the first part of the interface, that
Table View that will show everything.
| | 01:19 | In part two we will add code
to fetch into this Table View.
| | 01:22 | We will see a new object which will help us connect our
fetch results into an iOS table view controller.
| | 01:29 | Next, from the Table View, we will see how
to add a plus button, create a new object
| | 01:34 | using a new view controller,
and how to format that.
| | 01:38 | After that, we'll see how to
delete directly from the Table View,
| | 01:42 | then connect and configure a detail view
to show individual details and finally shift
| | 01:47 | the screen into an alternate mode
to let us edit an existing entry.
| | 01:52 | So classic CRUD: Create, Read, Update, Delete,
all done from the UI, and along the way, we
| | 01:58 | will see a few things like how to make sure
your Table View is always being updated in
| | 02:02 | line with any changes to the underlying objects, how
to create default values for your objects, and so on.
| | 02:08 | This will be a great foundation for adding some more
advanced features like undo, redo, and validation.
| | 02:14 | Now if you are planning to follow along in
Xcode, have your pause button handy.
| | 02:18 | There are a lot of steps and a decent amount of code.
| | 02:21 | I will explain everything on adding, but
consider just watching straight through once to get
| | 02:26 | the hang of what's going on before
attempting to duplicate everything. So let's begin.
| | 02:30 | I am going to open up Xcode and create a new iOS
project, an empty project but with Core Data support.
| | 02:38 | So selecting empty application, click Next,
I'll call this CDCourses for Core Data Courses.
| | 02:45 | You can, of course, call it whatever you like.
| | 02:47 | I will make sure I am doing it just for the
iPhone and that Use Core Data and Use Automatic
| | 02:52 | Reference Counting are checked, Unit Tests
is unchecked, as is the local git repository.
| | 02:59 | And I'll just save this to my Desktop.
| | 03:02 | So if I take a look over here in the
Navigator on the left, we can see I don't really have
| | 03:06 | a lot of things here.
| | 03:07 | There is the standard code added to the
AppDelegate that's preparing our three essential
| | 03:13 | Core Data objects, the ManagedObjectModel, the
PersistentStoreCoordinator, and of course
| | 03:19 | the ManagedObjectContext.
| | 03:21 | But when creating an empty
application, I have no user interface at all.
| | 03:26 | So there's no storyboard, no xib, no view
controller classes, that's all up to us, but
| | 03:31 | first let's do the data model.
| | 03:34 | This file is completely empty.
So I have to add the first entity.
| | 03:37 | I'll just add one called Course, and I will
give it three simple attributes, title, author,
| | 03:47 | and releaseDate, and not surprisingly, this
will be author of a String, releaseDate is
| | 03:55 | a Date, and title is a String.
| | 03:58 | Optionally, at this point I could
also create some default values for this.
| | 04:02 | So by selecting title, I can go over here
and say the Default Value is, and I'll put
| | 04:07 | TITLE in uppercase just so it will be obvious
when we are using it later, same with AUTHOR.
| | 04:15 | Now if I am setting a Default Value for
releaseDate, I do have a place I can type one in here,
| | 04:19 | but I'd have to really type a literal value, a
particular date, and that might not be what I want to do.
| | 04:26 | A little later on I'll show you how you can
actually do your own default values in code.
| | 04:32 | For now I'll do a basic date, 2012-01-01.
So this is a purposefully simple entity.
| | 04:40 | We are going to see how to hook up these
three attributes up to a user interface.
| | 04:43 | It doesn't really add anything
just if I had 10 attributes.
| | 04:47 | What I'm going to do is
generate a class from this.
| | 04:50 | It's not going to add any
custom behavior right now.
| | 04:52 | I just want some named properties.
| | 04:54 | So from the Editor, I'll say to
Create NSManagedObject Subclass.
| | 04:57 | You want to make sure of course that your
attributes are named correctly and they have correct values.
| | 05:04 | I want to do this for Course and save
these into my project, click Create.
| | 05:10 | So I have Course.h and Course.m
giving me my named properties.
| | 05:15 | Let's begin a user interface.
I'm going to do this with Storyboard.
| | 05:18 | So from the File menu, I'll add a new file.
| | 05:21 | This will be a Storyboard which you will find
under the User Interface section of iOS. Click Next.
| | 05:28 | This will be a Storyboard for the iPhone, and
I'll call this MainStoryboard, click Create.
| | 05:35 | A storyboard gets created
with nothing on it at all.
| | 05:38 | It's a completely blank canvas.
| | 05:39 | So I'm going to make sure my Utility
section is open, switch to the Object Library, and
| | 05:45 | I'm going to drag on a Table View Controller,
because I know that the first thing I want
| | 05:49 | to see is a list of different options.
| | 05:52 | We will need a lot more than this,
but we need something to begin with.
| | 05:56 | But if I add a Storyboard to my project, it's
going to be vital that I tell this project's
| | 06:01 | Info.plist file about its existence.
| | 06:04 | So in my Supporting Files section, I'll drop
into the Info.plist for this, and I need to
| | 06:10 | add a new entry to say
what the MainStoryboard is.
| | 06:13 | There are multiple ways I can do it.
| | 06:15 | One of the easiest, go into the Editor section,
make sure I click in there in the middle section,
| | 06:20 | and then say Add Item.
I do have the pop-up here.
| | 06:23 | What I am looking for is Main storyboard, and
if I just type the lowercase m, it won't find it.
| | 06:28 | But if I type Shift+M for
the uppercase one, there we go.
| | 06:32 | I'm looking for Main storyboard file base name.
There are a few options there.
| | 06:37 | What you'll find is one is
specifically for iPhone, the other for iPad.
| | 06:41 | I just want the generic one,
Main storyboard file base name.
| | 06:45 | This is going to be a String. That's correct.
| | 06:47 | I'll double-click over here, and we give it the name
of the file I just created--which was MainStoryboard--
| | 06:55 | now there is a storyboard
named in our Info.plist file.
| | 06:59 | This will be loaded in automatically.
| | 07:02 | So what I can actually do is go into my AppDelegate
and remove some code, because right now AppDelegate.m
| | 07:08 | in the application did finish launching with
options has a few lines here to just create
| | 07:13 | a window object and set its
background color, make it visible.
| | 07:17 | I don't need any of this because
that Storyboard is going to be loaded.
| | 07:21 | So I am deleting everything
apart from the words return YES.
| | 07:25 | I'm going to save this and just go ahead and
run it to see if I can have the telltale lines
| | 07:32 | of the Table View in my Storyboard.
Okay, it looks good.
| | 07:35 | It's giving me a warning over there,
but we will take care of that in a moment.
| | 07:38 | I can tell that my Storyboard is loading,
and it's loading the Table View Controller.
| | 07:43 | Quit out of that and back into the application.
| | 07:46 | See, we will need a custom class for this
Table View Controller as this is going to
| | 07:52 | do most of the work, and we need a
place to write some custom code for it.
| | 07:56 | So I will add a new file.
| | 07:59 | Under Cocoa Touch, select Objective-C class.
| | 08:03 | I'm going to create a class
called CoursesTableViewController,
| | 08:07 | and it's very important that this is a subclass
of UITableViewController, not just UIViewController.
| | 08:13 | I don't need either of these checked,
because everything is in my Storyboard, and it's an
| | 08:17 | iPhone one we are using, and click Next.
| | 08:21 | And yes, of course, I want
to add this to my project.
| | 08:24 | As soon as I've done that, I'm going to jump
back into the storyboard, because right now
| | 08:28 | this is an instance of the generic UITableViewController,
and we need it to be an instance of our new subclass.
| | 08:36 | So down in the bottom bar, I will select
where it says Table View Controller and open up
| | 08:41 | my Utilities Inspector, making sure that's
selected in the third part of the Inspector
| | 08:49 | here where we've got the Identity Inspector. I
am going to select the dropdown, and I should
| | 08:54 | see legitimate classes pop up, including this
new one the CoursesTableViewController. Save that.
| | 09:02 | I'm still expecting a couple of warnings that we've
got, potentially incomplete method implementation
| | 09:07 | just because we are not actually
providing any data that are this table view.
| | 09:11 | That's to be expected because it's what
we are going to do in the next section.
| | 09:16 | The other warning that I am getting is on
the storyboard here that Prototype table cells
| | 09:20 | must have a reuse identifier.
Well, I can go ahead and do that now too.
| | 09:24 | So selecting the cell itself, coming over
into the Attributes Inspector, the fourth
| | 09:30 | one, again I've got to be
careful what I've selected.
| | 09:32 | We will give it a Reuse Identifier
of Cell with an uppercase C.
| | 09:38 | Now the last thing I'm going to do before we go
ahead into the next section is a bit of prep work.
| | 09:45 | See, if I jump over into the AppDelegate,
currently the AppDelegate has a property for
| | 09:50 | the ManagedObjectContext, the beating heart of
Core Data, the one we will need to use a lot.
| | 09:58 | AppDelegate is a good place for this object,
but we will also need to get to this repeatedly
| | 10:02 | from that new Table View Controller class
so we can call save and call fetch and so on.
| | 10:09 | The most convenient thing to do is that in
our new Table View Controller I am going to
| | 10:13 | add a property to hold a reference to this.
| | 10:16 | So jump over to the header file and in here
just add a property, so a nonatomic, strong
| | 10:23 | property of type NSManagedObjectContext,
I will call it managedObjectContext.
| | 10:30 | I don't need to synthesize for this
because I am not creating any custom accessors.
| | 10:34 | I will just let Xcode synthesize it.
| | 10:36 | Now back over in the AppDelegate, what I'm
going to do is pass over a copy of that.
| | 10:42 | So jump into the AppDelegate.h and just do
an import so it knows about the new class
| | 10:47 | that I recently created, CoursesTableViewController,
and then jumping over to the implementation.
| | 10:55 | I'm interested in the application didFinishLaunching
section, and what I want to do is grab hold
| | 11:00 | of that new Table View Controller so I can
access its properties and pass it a reference
| | 11:05 | to the ManagedObjectContext.
| | 11:06 | So I am grabbing it from the AppDelegate's
self.window.rootViewController and casting
| | 11:13 | it as an instance of CoursesTableViewController
so I could then quite quickly go ahead and
| | 11:20 | set its property, the one we just created of
ManagedObjectContext = self.managedObjectContext,
| | 11:27 | the one in the AppDelegate.
| | 11:29 | Now this will work fine right now because we
only have one view controller in our Storyboard.
| | 11:35 | If I were to embed that in a Navigation
Controller, I'd have to revisit this code.
| | 11:39 | Well, while I'm thinking about it, I know
that I will need a navigation controller,
| | 11:43 | because we are going to go from that table view
to a detail view to add screen to an edit screen.
| | 11:48 | So let's just do that prep now.
| | 11:50 | I am going to jump into the Storyboard,
select the Table View Controller, easiest way to
| | 11:55 | click down here in the bar at
the bottom to select this object.
| | 12:00 | Sometimes that's a little difficult
if you're not in at the 100% view.
| | 12:04 | So you can also use the document area here.
| | 12:10 | With that selected, go up to Editor,
click Embed In > Navigation Controller.
| | 12:17 | What that will automatically do is create the
Navigation Controller, set it as the initial
| | 12:21 | view controller, and nest ours inside it.
| | 12:25 | It's also given me the start of the navigation
bar at the top here, which is fine, it's convenient.
| | 12:30 | I am going to just double-click
that and rename it to Courses.
| | 12:33 | I don't have to do that, but
it's quite conventional to do so.
| | 12:37 | Then jump back into the AppDelegate, because
this code that I just added is no longer going
| | 12:42 | to work, because we will not be
the rootViewController right now,
| | 12:47 | I'll add a little code to grab it.
| | 12:53 | So we are using the same kind of code, but
this time to grab the UINavigationController
| | 12:57 | that's at the top, and then what I can do
in the next line, I'll still need to cast to
| | 13:02 | a CoursesTableViewController, I'll change it
to access this new object called nav, there
| | 13:09 | we go, space, it will have a collection of
view controllers, and I'm interested in whatever
| | 13:15 | the first one is objectAtIndex:0.
| | 13:18 | It might be little difficult to read just
because I have restricted screen real estate
| | 13:24 | here, but hopefully this'll
make it a little bit clearer.
| | 13:27 | Line 19, grab the Navigation Controller, at
line 20 we grab our CoursesTableViewController
| | 13:32 | out of it, whatever is at position 0 and then
line 21, we pass a reference to ManagedObjectContext,
| | 13:38 | and that's all the prep I want to do for now.
| | 13:40 | Next, let's see how to fetch
into this Table View Controller.
| | Collapse this transcript |
| Configuring the fetched results controller| 00:00 | When the application loads, and this Table
View Controller appears, we want it to fetch
| | 00:04 | all our course entities
out of the Core Data store.
| | 00:08 | Now I realize we have nothing to fetch yet,
but we know we will, so let's do the groundwork.
| | 00:12 | I am making the assumption that you're
already reasonably familiar with iOS Table Views,
| | 00:17 | that they can be split into sections that we have
certain named methods to use to fuel them with data.
| | 00:23 | If that's not the case, take a look at the Table
View chapter of iOS SDK Essential Training first.
| | 00:29 | So we're going to fuel this Table View
Controller, and that means pretty much everything that
| | 00:34 | we do will be part of the
CoursesTableViewController class here.
| | 00:39 | And this is where we want to
execute a fetch out of Core Data.
| | 00:42 | Now you saw in the section on Core Data
Fetching that when you execute a fetched request, what
| | 00:48 | you get back is an array of managed objects.
| | 00:51 | Now an array is fine, and that can be useful,
but manually writing code to connect an array
| | 00:56 | to a UITableViewController is kind of tedious.
| | 01:00 | So we're going to use a brand-new additional
object, the NSFetchedResultsController, and
| | 01:06 | I'll just write a property for that.
| | 01:13 | Now make no mistake, we still use standard
Core Data Fetched Request. We still use standard
| | 01:18 | iOS Table Views and Table View Controllers.
| | 01:21 | This object will make it much
easier to tie the two together.
| | 01:25 | The entire existence of the NSFetchedResultsController
class is to tie Core Data Fetched Request
| | 01:33 | to UITableViews in iOS.
| | 01:35 | So that's in my header file. I am going to
jump across into my implementation and add
| | 01:39 | a synthesize statement here.
| | 01:41 | While I don't always have to do synthesize
statements anymore, I am going to do one this
| | 01:46 | time because I want to use the underscore
format for the internal instance variable
| | 01:52 | here, and it's also because I'm going to
write my own lazy instantiation method to create
| | 01:58 | this fetchedResultsController
object and create it the right way.
| | 02:01 | Now I can put that method anywhere in this class.
I'll put it down towards the bottom
| | 02:06 | here, let's say just above this lowest Table
view delegate section, and it's a good idea
| | 02:12 | to put in a couple of pragma marks.
| | 02:13 | If you don't use pragma marks, they just
allow us to create our own area in the jump bar,
| | 02:20 | so we can have a section here. I'll just
call it the Fetched Results Controller section.
| | 02:27 | And that means any methods that I put down
here will actually appear in this section
| | 02:31 | here in the jump bar, making it easier to get
around. It has no impact on actual behavior
| | 02:36 | of the application, you don't have to do it,
| | 02:39 | but what I do have to have is a method that will
return an instance of NSFetchedResultsController
| | 02:45 | that's obviously a pointer, and the
method is called fetchedResultsController.
| | 02:52 | So the first code, ask is there
anything already in that instance variable?
| | 02:57 | If it's not equal to nil, there is something
there, we have a fetchedResultsController,
| | 03:01 | just return it, we're done.
| | 03:03 | But if we get past that closing brace of the
if statements and we don't have one, we're going
| | 03:07 | to have to make one.
| | 03:09 | So the question is, how do you
make a fetchedResultsController?
| | 03:11 | Well, we saw how to make a fetched request,
and in fact, most of it is identical to that,.
| | 03:17 | We actually need a fetched request and a sort
descriptor to create a fetched results controller.
| | 03:23 | So actually, to save us a little time, while I
could write it out in full, what I am going
| | 03:27 | to do is grab my Code Snippet Library, filter
on fetch, and I'm going to find not the Basic
| | 03:37 | Fetch, not the Predicate one. I don't need
a Predicate here. What I do need is Core
| | 03:41 | Data Fetch with Sorting, so I am going to
drag that over after the if statement and
| | 03:47 | get rid of the memory management code. I don't
need to release anything. I have arc turned on.
| | 03:52 | And I actually don't need the part that executes
that. What I really just need is the FetchRequest,
| | 03:57 | EntityDescription, and
the SortDescriptor section.
| | 04:01 | So I've got all of that. That will work.
| | 04:03 | So I've got my FetchRequest here. What
entity is this for? It's going to be for Course.
| | 04:08 | What ManagedObjectContext do I have? Well,
that's a property of self, so I'll just say
| | 04:12 | self managedObjectContext.
| | 04:14 | Don't have to use the square brackets,
could have just said self.managedObjectContext,
| | 04:18 | that would have worked.
| | 04:19 | I do have to have a sort to
retrieve these in a particular order.
| | 04:22 | I am just going to sort on author name.
| | 04:25 | So the attribute is author, however it's defined
in your data model. Mine is, of course, lowercase.
| | 04:31 | And let me just balance out this code here,
go to the Editor menu, and just say Re-Indent.
| | 04:37 | So I now have my fetchRequest object, my entity
description, my sortDescriptor all connected.
| | 04:44 | So instead of executing this fetch directly,
what I'm going to do is use it to create this
| | 04:49 | fetchedResultsController, this new object
that we're going to tie to the Table View.
| | 04:54 | So this is the new part. We're using that
instance variable _fetchedResultsController.
| | 05:01 | It has a fairly long initializer called
initWithFetchRequest:managedObjectContext:
| | 05:09 | sectionNameKeyPath:cacheName.
Well, a couple of these are pretty obvious.
| | 05:14 | We need to initialize this with a fetchRequest.
Well, coincidently we just made one, so we
| | 05:20 | have a fetchRequest that was allocated on line
121 just called fetchRequest, and that will do.
| | 05:27 | Next one, managedObjectContext, well, again
it's a property of self, so either using the
| | 05:31 | square brackets or
self.manage dObjectContext, we have that one.
| | 05:36 | Then we have this sectionNameKeyPath,
now what does this mean?
| | 05:40 | The impact of setting this is that we can
fetch our results back in sections, and we're
| | 05:45 | talking about sections here in an iOS Table
View, the way that it can be automatically
| | 05:51 | split up into sections.
| | 05:53 | If I want that to happen, say that I want
to split the authors into sections based on
| | 05:57 | the author names, so all the courses for a
particular author show up in their own section,
| | 06:02 | then I just name the attribute we want to
split into sections here, and it will be author.
| | 06:07 | I don't have to do that. I could just say nil, and
that would bring them back in just one long list.
| | 06:12 | And finally, cacheName. If we had an awful
lot of data, we'd be playing around with this.
| | 06:17 | I'm going to leave it for now. We don't
need that, so I'll just set that to nil.
| | 06:21 | And that is our
fetchedResultsController object created.
| | 06:24 | Now because this is just an accessor method that
needs to return it, I need to have a return statement.
| | 06:29 | Now you might be wondering, well, did
that execute it just by creating it?
| | 06:34 | No, this is just an accessor method. This
is just for anyone that wants to ask for the
| | 06:39 | fetchedResultsController property.
It will create it when needed.
| | 06:42 | We're going to execute this
in a little while, but not yet.
| | 06:46 | But that's that method done.
| | 06:47 | Now what I can see up here at the top is I
still have the exclamation mark letting me
| | 06:51 | know there's a couple of warnings here for
potentially incomplete method implementation.
| | 06:57 | So let's take care of those now. We
are still in CoursesTableViewController.
| | 07:01 | The first one is the numberOfSectionsInTableView
method, how many sections does this Table View have?
| | 07:07 | And of course we have no idea. It will
differ depending on how many objects
| | 07:11 | we have, how many authors that we have.
| | 07:14 | But it's fine. What we can do is ask the
fetchedResultsController how many sections do you have?
| | 07:20 | So here, all I am going to do is return the
sections property which is an array and has a count.
| | 07:27 | Well, that's it. I can
get rid of the warning now.
| | 07:31 | That line will tell the Table
View how many sections are in it.
| | 07:34 | Well, onto the next one, the
classic tableView:numberOfRowsInSection.
| | 07:38 | We'll need one more line, so we'll make it 2 this time,
NSFetchedResultsSectionInfo secInfo objectAtIndex.
| | 07:54 | So what's going on here? It might look a
little odd, but line 61 is saying we are working
| | 07:58 | with collections of collections here.
| | 08:00 | What we're doing is we're asking the
fetchedResultsController for its sections array, and we're going into
| | 08:06 | a particular one based on the objectAtIndex
section parameter, and again, this is being
| | 08:12 | passed in, this will be called
automatically for every single section, so we'll always
| | 08:16 | have zero, then one, then two, then three.
| | 08:19 | So we'll find that information, and what we're
doing is we're taking the results and putting
| | 08:23 | in an ID called secInfo, and that has to comply
with the NSFetchedResultsSectionInfo protocol,
| | 08:31 | which is why it's in the angle brackets here.
| | 08:33 | Because I don't really care about what this is,
I just need to make sure that it will respond
| | 08:39 | to me asking it how many objects do you have?
| | 08:42 | So in this case, we'll ask what are your
number of objects? We'll return that, done.
| | 08:47 | Now we'll go on to the next one.
| | 08:48 | This isn't giving us a
warning, but we need to configure.
| | 08:51 | This is, perhaps, the most classic method that we need
for a tableView, the tableView:cellForRowAtIndexPath,
| | 08:58 | meaning what exactly are
the contents of each row.
| | 09:02 | So the first two lines are trying to de-queue a
re-usable cell, and after this we're going to configure it.
| | 09:08 | So I am going to configure the cell based
on objects in the fetchedResultsController,
| | 09:12 | this should be course objects there.
| | 09:14 | So I'll create a temporary course object pointer,
and that's not knowing what I'm talking about
| | 09:19 | because I haven't
imported my course header file.
| | 09:22 | So let me just zoom up to the top and add an
import statement for that, just for Course.h,
| | 09:31 | use the jump bar and jump back down into
cellForRowAtIndexPath and just ask the fetchedResultsController
| | 09:40 | what's at this particular indexPath right now.
| | 09:42 | And of course, this method is being called
repeatedly for every row in every section
| | 09:48 | passing in the indexPath.
| | 09:50 | That will give me the course object for this
particular row, and I can just start to configure
| | 09:56 | the individual cells here, so cell textLabel.text.
We're just going for basics here, equals course.title.
| | 10:03 | I am just going to double-check something
here because this is attempting to de-queue
| | 10:09 | a cell with the word Cell with an
uppercase C as the Reuse Identifier.
| | 10:15 | Let's just double-check that is the case in
our Storyboard, so I am going to just jump
| | 10:19 | across here, look at the Storyboard, select the
Table View cell, and bringing up the Attributes
| | 10:26 | Inspector, yes indeed, my Identifier is
Cell with an uppercase C, so they do match.
| | 10:34 | Back across, and that will work.
| | 10:37 | Let's delete a couple of
the spare lines. All right!
| | 10:42 | These are all the methods that I have to have.
| | 10:44 | How many sections do I have, how many rows are
in each section, what's the contents of each row?
| | 10:48 | I am going to add one more thing. This is
optional, but seeing as I am fetching the
| | 10:53 | results in sections, I'm going to add a
little code to tell the Table View what to expect
| | 10:57 | at the top of each section, what title to use.
| | 11:01 | This is going to be a method that returns
an NSString, which is obviously a pointer,
| | 11:06 | and the method is called tableView, there we go. We
should expect it to pop up, tableView:titleForHeaderInSection.
| | 11:15 | There are some very similarly named
ones. We want titleForHeaderInSection.
| | 11:18 | Again, as soon as I have this method, it
will be called automatically, because we are a
| | 11:23 | delegate and a data source for the tableView.
| | 11:25 | And what we want to do is jump into the fetchedResultsController
into the right section, into the right object, and get its name.
| | 11:34 | I'll just compress this as much as I can here, go
into our array of sections, find the objectAtIndex
| | 11:44 | called section that's the parameter that's
being passed into this method, and find its
| | 11:49 | property called name and return that.
| | 11:54 | And the last thing we need to do, even if I've got
everything hooked up is we are never executing
| | 11:58 | the fetchRequest, we're never getting
anything in that fetchedResultsController.
| | 12:03 | So we need to execute or perform the fetch.
| | 12:07 | Well, most convenient place for me here will be to
jump up to the viewDidLoad section and do it there.
| | 12:12 | So in viewDidLoad right before the end, we
perform a fetch quite similar to performing
| | 12:18 | a save on managedObjectContext, meaning I
am going to create an NSError pointer and
| | 12:23 | pass that in and let it tell
us if anything wrong happened.
| | 12:32 | So create an NSError pointer and then call
the fetchedResultsController performFetch
| | 12:38 | method passing in the
address of that error object.
| | 12:42 | That will return yes if it's successful,
no if it isn't, so I'm negating the check.
| | 12:47 | So if it's not yes, then we're going
to pop up an NSLog and an abort message.
| | 12:52 | I am going to save and build that and go ahead and run
just to see if it will give us any errors in the code.
| | 13:01 | Everything seems to load. I don't
see any diagnostic messages popping up.
| | 13:05 | But of course, here's the problem: we have no actual
data to fetch, so we'll fix that in the next step.
| | Collapse this transcript |
| Creating and configuring the modal view controller| 00:00 | So next, I'm going to go into the Storyboard,
and we're going to add a new view controller
| | 00:05 | to this that we'll
configure as a at course screen.
| | 00:09 | So with the Storyboard selected, I am going
to open up my Object Library, and I want to
| | 00:14 | drag on a normal View Controller, not a Table
View Controller--we could do that, but normal
| | 00:19 | will work just fine.
| | 00:19 | I am going to shrink mine down a bit
because I can't really see very much here.
| | 00:24 | So we have a View Controller, now
the question is how does this open up?
| | 00:28 | Well, we'll need to do it from that first
main Table View Controller, because that's
| | 00:33 | what's going to appear as
soon as we open the app.
| | 00:36 | So what I am going to do--let's zoom back
in to do it--is I am going to add a button
| | 00:40 | up here on the navigation bar,
just a plus button.
| | 00:44 | So in my Object Library I'll filter down by
Bar button Item, that will do, drop it up here,
| | 00:52 | and I'm going to change that
to the Add sign. That will do.
| | 00:57 | I want this to open up this new View
Controller so what I am going to do is Ctrl-drag from
| | 01:02 | the button--always be careful on what you
have selected. It should be highlighted down
| | 01:05 | onto the new View Controller.
What kind of Segue it asks.
| | 01:09 | It could be push, could be modal, could be custom.
| | 01:12 | A push segue would be the
easiest here, but let's do this right.
| | 01:17 | For adding the recommendations, usually a modal
segue from the bottom up, which means we won't
| | 01:23 | automatically be embedded in the navigation
bar so that doesn't appear here at the top,
| | 01:27 | so I'm going to have to do a
little bit of user interface work here.
| | 01:31 | First, I am actually going to add a Nav Bar
because that's the kind of look and feel that
| | 01:35 | I want, so I'll drag that on,
double-click it to re-title New Course,
| | 01:43 | and then I'll add two bar buttons items onto
this, one on the left, and one on the right.
| | 01:51 | The left one I'll select, I want that one
to be Cancel, which is a pretty standard one,
| | 01:56 | and the right one to be Save,
which is also fairly standard.
| | 02:01 | And then back into my regular Object Library, what I
want to do is add a few Labels and Text Fields here.
| | 02:08 | I am going to highlight all these text fields and
actually change them to a borderless border style.
| | 02:18 | Don't have to do that, but why not?
| | 02:21 | And then check them to make sure that they
Clear when editing begins, because I'm going
| | 02:25 | to be populating them with default information.
| | 02:28 | Again, don't have to do
that, that's just optional.
| | 02:31 | Now I hope you'll forgive me in this case
to have a complete UI but not a polished one.
| | 02:35 | I would normally associate Release Date with
a date picker, but for the purposes of this
| | 02:40 | demo, I am just going to leave it as a
normal text field, and I'll leave the date picker
| | 02:44 | idea as an exercise for you.
| | 02:45 | Now I do want these defined as outlets, so
what I'm going to do is switch into assistant
| | 02:50 | view and then click and drag over here. Let's
make sure the right one is selected. At the moment
| | 02:57 | I don't have a custom class, I really need one.
| | 03:00 | Now that would be a big problem
trying to click and drag from this.
| | 03:04 | So, back over here in Xcode, I am going to
add a new file. We want to write some custom
| | 03:09 | behavior for this View Controller, so
we'll add a new Objective-C class, I'll call it
| | 03:16 | AddCourseViewController, I want to make
doubly sure that this is not UITableViewController,
| | 03:22 | it's just a subclass of UIViewController, both
of these should be unchecked, and I'll create it.
| | 03:30 | The last thing that I'll need to do to make
sure everything hooks up correctly is to select
| | 03:35 | this View Controller and make sure that in
the Identity Inspector--again, it's lost it.
| | 03:43 | It thinks I've got in a text field
here, so select the View Controller.
| | 03:46 | Right now it's the generic class UIViewController.
I need it to be AddCourseViewController.
| | 03:51 | Without this, I'm not going to have
any of my custom code working.
| | 03:55 | And back again, what I wanted to do here was
switch into Assistant View, so hopefully this
| | 04:00 | is going to be the right one now. With this
selected, we have the AddCourseViewController.
| | 04:04 | That looks right, and what I'll do is select
the first one, the Title text field, Ctrl-drag
| | 04:10 | that into the interface, set that
up as an outlet called titleField,
| | 04:16 | take the second one, Ctrl-drag in there, and
not surprisingly this will be authorField,
| | 04:22 | and the third one for date, and I think you
know where I'm going with this, dateField.
| | 04:27 | Well, before we go much further, what I am
going to do is just go ahead and run and make
| | 04:32 | sure the segue is working, and
there is no issues right now.
| | 04:35 | If I click that plus button, we
should segue up from the bottom.
| | 04:39 | So the idea is when we do this, we're going
to create a new course object back on that
| | 04:44 | TableViewController, and we'll
pass it in to this view controller.
| | 04:48 | If they manipulate it and hit Save, we
will go ahead and save that new object.
| | 04:52 | If they hit Cancel, well, we've got an object
we don't want, so we'll go ahead and dispose of it.
| | 04:57 | But neither of these buttons are going to do
anything just yet, and we need to make sure that they do.
| | 05:03 | So back into the Storyboard, I'm going to
switch again into Assistant mode and Ctrl-drag first
| | 05:09 | from the Cancel button into my associated
header file, making sure this is not an Outlet
| | 05:14 | but an Action, and I'll just call this Cancel.
| | 05:18 | And again, drag from Save, again making
sure this is not an Outlet, but an Action.
| | 05:26 | We all accidentally create Outlets for
buttons where we don't want to, but I am trying to
| | 05:30 | avoid that here, and we'll call that Save.
| | 05:33 | The idea being that if I jump into my implementation
file for that, that I should now have a couple
| | 05:40 | of methods down here for Cancel and Save.
| | 05:47 | So we're going to there dismiss the modal
view controller and remove the object, whereas
| | 05:51 | when I hit Save, we still want to dismiss
the model view controller, but we want to save
| | 05:56 | the context. Again, we save the
context rather than the object.
| | 06:00 | But here is the thing, so how do we go ahead
and dismiss, we saw that it isn't doing it yet?
| | 06:04 | Well, dismissing modal view controllers is
kind of a pain in iOS. You don't do it directly.
| | 06:10 | This model view controller shouldn't
dismiss itself, it should be done with delegation
| | 06:14 | so that the same view controller that was
responsible for popping it up, which is that
| | 06:19 | CoursesTableViewController is also going to be
the one that's responsible for dismissing it.
| | 06:25 | So we need to do a little prep in this new
AddCourseViewController to allow that to happen.
| | 06:30 | Well, first, I know that I'm going to be
passing in a course object, so I am going to just
| | 06:35 | jump over into my
AddCourseViewController header file here.
| | 06:39 | And if I am going to have a course
object, I better do an import statement.
| | 06:42 | And I am also going to create a property for that course object,
just going to be one at a time, so I'll call it current course.
| | 06:54 | However, the main thing that we need to do
here--which is not specifically about Core Data,
| | 06:58 | just about the way that modal view controllers
should be done in general--is we're going to
| | 07:03 | create this new course view
controller as having its own delegate protocol.
| | 07:08 | Now I am going to actually declare it up before
the word @interface, so here I'll put protocol,
| | 07:14 | and usually you just name it after this class,
so we'll call it AddCourseViewControllerDelegate,
| | 07:22 | and that's all I need there right now.
I am going to put the methods below.
| | 07:28 | The reason that I have to declare it up
here is I'm going to actually use it here in a
| | 07:32 | section, one of the properties in here apart
from my UI properties here, titleField, authorField
| | 07:37 | and dateField, and apart from this Course,
one is I need something that will actually
| | 07:41 | hold a reference to whoever
is going to be my delegate.
| | 07:44 | Now this should be the CourseTableViewController,
but could be anything, so I'll create a property.
| | 07:52 | This can just be weak.
| | 07:56 | So just an ID of something that volunteers
to be my delegate, and we'll call it delegate.
| | 08:03 | Now below this I can finally define
what that actual delegate needs to provide.
| | 08:06 | What I'm really wanting to do is say that
this modal view controller can do two things,
| | 08:12 | it can say hey, somebody clicked my Save
button, or hey, somebody clicked my Cancel button,
| | 08:17 | and it's going to pass off those behaviors
to whoever is volunteering to be the delegate.
| | 08:22 | So let's go back to the
protocol. Let's do it below.
| | 08:29 | So in here I need a list of two methods
that the delegate will need to provide.
| | 08:34 | They both can return void, the first one will be--
let's call it addCourseViewControllerDidSave.
| | 08:39 | It doesn't matter, you could just say save,
or I saved, but usually they're named something
| | 08:45 | like this, and that will take no arguments.
| | 08:49 | And the second one will also return void, and
we'll call this one AddCourseViewControllerDidCancel.
| | 08:56 | Somebody clicked the Cancel button.
| | 08:58 | But seeing as what I'm doing is I am creating a
new object and passing it in, well, if somebody
| | 09:03 | says they want to save, I
can just go ahead and save.
| | 09:06 | They said they want to cancel. I have to
make sure that that object is removed, because
| | 09:11 | otherwise, it will be sitting around in a
managed object context, we don't want that, so I'll
| | 09:15 | have a parameter for this that they can pass
back which will be a pointer to a course object.
| | 09:24 | And this will be the course that needs deleting.
And that's my protocol defined.
| | 09:29 | Now if you don't do much with protocols, this
probably looks a little confusing, just a way
| | 09:33 | we can really pass around behaviors in between
this modal view controller and the table view
| | 09:39 | controller that's going to open it.
So how do we go ahead and do that?
| | 09:42 | Well, I'm going to jump across into that CoursesTableViewController,
which is where most of the rest of the work happens.
| | 09:49 | First, jump over to the header file, because there
is a couple of things that don't need to happen here.
| | 09:53 | One, if this one is going to be tightly
wound with that AddCourseViewController, it better
| | 09:59 | know about it, so we'll
import the header file.
| | 10:02 | Next, we're going to volunteer and say we
are able to be a delegate for that course, so
| | 10:08 | AddCourseViewControllerDelegate in the angle brackets.
| | 10:12 | We're not done yet, but at
least we can support that behavior.
| | 10:15 | Well, how is this all going to work?
| | 10:17 | Well, the whole idea is that when I run this
application, I am just going ahead and running
| | 10:22 | it to kind of remind you
what we're trying to do here.
| | 10:25 | When we press this button, we are going to
create an object, pass it into that modal
| | 10:30 | view controller, and tell the modal
view controller, hey, I am your delegate.
| | 10:35 | So that when somebody in here clicks Save
or Cancel, we're passing those notifications
| | 10:40 | back, and we can either Cancel and dismiss
and get rid of that object, or we can Save
| | 10:45 | it to the persistent store.
| | 10:46 | So I am just going to quit back out of this,
going back into CoursesTableViewController,
| | 10:51 | because the first question I have is well,
when do I create that object and pass it in?
| | 10:56 | Well, it's going to be when the segue happens.
| | 10:59 | So what I need to do in my implementation
is have a prepare for segue method here.
| | 11:04 | Now in my implementation file I am already
being told I've got an incomplete implementation
| | 11:08 | because I haven't actually provided the implementation
for the ViewControllerDidCancel, ViewControllerDidSave.
| | 11:16 | Now we'll do that in a moment.
| | 11:18 | What I need first is to provide methods to
say when we're about to do a segue, I am going
| | 11:23 | to create a new course object and pass it in.
| | 11:25 | And that's a built-in
method called prepareForSegue.
| | 11:30 | So to void method, as I start typing P-R-E-P,
it should pop up just click to make those
| | 11:37 | go away for a moment.
| | 11:38 | Well, what I first want to do is make sure
it's the right segue, we could potentially
| | 11:42 | have multiple different segues going on.
| | 11:45 | Am I doing the modal one to add a new course?
Am I doing a push one to show the details
| | 11:50 | of a course? So I better
ask if it's the right one.
| | 11:56 | We'll always have something called segue
identifier being passed into this method, so I can ask
| | 12:02 | is it equal to a particular string?
I am going to just call it addCourse.
| | 12:06 | Now the question being,
where does that come from?
| | 12:08 | Well, I'll show you.
| | 12:10 | If I jump back over into my Storyboard, this
is the object that's my segue here, so when
| | 12:18 | I'm clicking around I should be able to
click on the icon here and select it, and if I do
| | 12:22 | that and then open up my utilities panel, I
should be able to give this Storyboard segue
| | 12:28 | an identifier if I am in
the Attributes Inspector.
| | 12:32 | So I'm going to call this addCourse, lowercase a,
uppercase C, so that back in my code I can ask for it.
| | 12:41 | So if it's addCourse, I know
we're in the right of segue.
| | 12:45 | So the code I am adding here on line 24, I
am going to just create a new pointer to an
| | 12:51 | AddCourseViewController, I am going to get
that from the destination view controller
| | 12:54 | property of the segue.
| | 12:55 | We know that we're segueing to the right place, so this
gives me a handle on the view controller we're going to.
| | 13:01 | Then with that, I can reach in and
set its delegate to self, meaning me.
| | 13:06 | We're about to create that new modal view
controller, and we're telling it your delegate
| | 13:10 | is me so look back to me when
somebody clicks that Cancel or Save button.
| | 13:15 | Now on line 127, well, this is where
creating the newCourse managed object, we're using
| | 13:21 | that NSEntityDescription, insertNewObjectForEntityForName
in a particular ManagedObjectContext, this
| | 13:28 | is what we saw when we were creating objects a few
sections ago. This is the classic Core Data way to do it.
| | 13:34 | And with that new object created, we can
reach into this modal window we're just about to
| | 13:40 | segue to and say, hey, here's your
current course, it's this object.
| | 13:44 | If you want to change it, change it. If you don't,
you can Cancel back out, and we'll get rid of it.
| | 13:49 | So I am going to jump back over it
into that AddCourseViewController.
| | 13:52 | This is the one we're modally segueing to.
| | 13:56 | I want to load some code to load
this new object into our interface.
| | 14:00 | So I am going to jump into viewDidLoad,
first I'll set the titleField, grabbing whatever
| | 14:06 | is in that newCourse object,
same with the authorField.
| | 14:10 | If you remember, I set default values on the entity
in uppercase, so we can hopefully see if this works.
| | 14:17 | However, I can't just do that with the date,
because that would be a little long just taking
| | 14:21 | a regular date string, so let me
just do a quick bit of date formatting.
| | 14:30 | So create a DateFormatter object, set its
format to a pretty conventional, say 2012-01-01.
| | 14:41 | And then convert that from the releaseDate object
in that currentCourse object that's being passed in.
| | 14:49 | Now that's going to work in our viewDidLoad
to grab the contents out of the object and
| | 14:54 | put it in our user interface, but when we
actually hit Save, what we're going to want
| | 14:58 | to do is do it the other way around.
| | 15:02 | And I'll just copy some of that formatted
code here so that we can convert the text
| | 15:13 | field of the date back into a date object.
| | 15:20 | So grabbing the contents of the dateField,
text field on the user interface, converting
| | 15:24 | that into a date object and saving that
in the currentCourse releaseDate property.
| | 15:30 | The question is, well, what else happens
in the save, are we saving the context yet?
| | 15:34 | No we're not doing any of that.
| | 15:35 | There is one more line that I need to add to
both the Cancel method and the Save method here.
| | 15:41 | It's not to actually dismiss this modal
view, because we're not supposed to do that.
| | 15:45 | What we're supposed to do is to announce to the
delegate that they are ready to take this for us.
| | 15:51 | So what I'll do is talk to my own delegate
property, and it's going to immediately pop
| | 15:55 | up my delegate methods here, which are
addCourseViewControllerDidCancel and addCourseViewControllerDidSave.
| | 16:01 | I'm in the Cancel section, which means I
do want to pass in the currentCourse object.
| | 16:09 | And very similar to that, in the Save method
we're just telling the delegate, hey, somebody
| | 16:13 | click this button, addCourseViewControllerDidSave,
you go ahead and do whatever you want to do.
| | 16:19 | Now we need to respond to these delegate
methods back over in the CoursesTableViewController.
| | 16:25 | So I'll jump back over to the CoursesTableViewController
file where it's still complaining to us that
| | 16:30 | we have an incomplete implementation,
so let's go ahead and fix that.
| | 16:36 | Now both of these are void methods, and they
both start with addCourseViewControllerDidSave,
| | 16:42 | addCourseViewControllerDidCancel, there we go,
there is the first one, there is the Cancel.
| | 16:46 | What it's doing is passing back a
parameter called courseToDelete.
| | 16:51 | What I can do is tell the managedObjectContext,
hey, we don't need that object anymore, you're
| | 16:56 | keeping track of it, it's in the scratch but go
ahead and get rid of it, we're not going to save it.
| | 17:01 | I am just going to create a temporary
variable to hold onto our context object.
| | 17:05 | I'll just call it context because then all I can do
is call the deleteObject method of that, not
| | 17:13 | deleted objects, but delete
object passing in courseToDelete.
| | 17:17 | That's it. That's me telling the managedObjectContext,
we don't need that object anymore, get rid
| | 17:24 | of it, we're not going to save it.
| | 17:26 | The last thing we need to do is dismiss that view
controller, which is going to be dismissModalViewControllerAnimated
| | 17:32 | with a Bool of yes.
| | 17:34 | Now, we've got one more method
to add, again a void method.
| | 17:40 | This one is the addCourseViewControllerDidSave.
| | 17:47 | And again, what I am really wanting out of
this is to grab hold of the managedObjectContext
| | 17:52 | and tell it that we're going to save.
| | 17:54 | First, we're going to create a no pointer to
an NSError. I am just going to grab a handle
| | 17:58 | on the managedObjectContext just
to make line 31 be a bit shorter.
| | 18:03 | And again, we're just
calling Save on that context.
| | 18:06 | It is a negative check, so I've got the
exclamation mark because I am only interested if there
| | 18:10 | was a problem. We're going to pop up the
NSLog message, otherwise we're assuming everything
| | 18:14 | is fine, we'll go ahead and just
dismiss that ModalViewController.
| | 18:18 | I am going to hit Save and run.
| | 18:25 | Pops up the Table View, nothing
there which is what I would expect.
| | 18:27 | I click the plus button, it looks like
we're loading in the default values, which is why
| | 18:32 | I wrote them in uppercase so
that they're hopefully recognizable.
| | 18:35 | I could do a bit of experimenting with
the font size here, but this will work.
| | 18:38 | If I click in there, say New Title, New Author,
I'm going to leave the default value of the
| | 18:46 | Release Date and hit Save.
We jump back, and I don't see anything.
| | 18:51 | Well, here is the problem. It probably did
work, but while this managedObjectContext
| | 18:56 | is saving--and I am assuming doing that correctly--
we need to have a little bit more communication
| | 19:01 | going on between the fetchedResultsController
and the Table View to make sure this is refreshed.
| | 19:05 | I am just going to try one more thing
before we go on to that, I am going to click the
| | 19:09 | plus and make sure that we
can just cancel out of it.
| | 19:12 | So at least the delegate
information seems to be working correctly.
| | 19:16 | So in the next section, we're going to see
how to do a bit better communication between
| | 19:20 | the fetchedResultsController and the Table
View, so it can refresh itself when it needs to.
| | Collapse this transcript |
| Responding to changes in the underlying objects| 00:00 | At the end of the previous example it
didn't look like objects were being created, but
| | 00:05 | in fact they were, and if I quit out of the
application and run it again, it will reload
| | 00:10 | promptly and fetch those
objects out of the data store.
| | 00:13 | So it is actually working, the
issue being it wasn't refreshing itself.
| | 00:19 | So we're going to make that work now, and we're
still going to work with that NSFetchedResultsController.
| | 00:24 | It does make our lives a lot easier when connecting
fetch results to a Table View, but like everything
| | 00:29 | else in iOS, it can exist at
several levels of complexity.
| | 00:33 | And if we want to get more complexity out
of it, like everything else in iOS, we're
| | 00:37 | going to use a touch of delegation.
| | 00:40 | So what I'm going to do is in this
course's Table View Controller where we're already
| | 00:44 | using the fetched results controller, I'm
also going to volunteer to be a delegate for it.
| | 00:49 | So everything I'm going to add will be in
the CoursesTableViewController class file.
| | 00:53 | First off, I'm going to jump into the header
file and volunteer, say that I can be a delegate
| | 01:00 | for NSFetchedResultsController,
and it should show up right there.
| | 01:04 | I'm already a delegate for that modal window,
so I'm putting the comma and then both of
| | 01:09 | these delegate protocols
inside the angle brackets.
| | 01:12 | But that's just me saying that I can
be. It doesn't mean that I am at yet.
| | 01:16 | So I'm going to jump across into the implementation,
and using the jump bar jump down to the Fetched
| | 01:21 | Results Controller section.
| | 01:24 | This is where I am instantiating the fetchedResultsController,
and this would be the place to tell it, hey, I'm your delegate.
| | 01:32 | So right before I return it, I'm going to
add a new line that points to it, accesses
| | 01:39 | its delegate property, and says, self,
look to me for any of your delegate methods.
| | 01:45 | So what does that mean?
Well, now it's going to call us.
| | 01:49 | It's actually going to call us several
different times in several different circumstances.
| | 01:53 | We have four delegate methods we're really
interested in here that if I add them, we'll
| | 01:58 | automatically be called in response to any
changes in that fetchedResultsController.
| | 02:03 | The first two are pretty simple.
| | 02:05 | I start typing void, and then what I'm
looking for begins with the word controller.
| | 02:10 | What we're looking for is the
controllerWillChangeContent, and the controllerDidChangeContent.
| | 02:16 | These are pretty straightforward.
| | 02:17 | What I'm going to do in here is just tell
the Table View that we are about to begin
| | 02:22 | some updates, and then I'm going to add
another method that will be called automatically at
| | 02:30 | the end of updates to the fetchedResultsController,
and we can tell the tableView we're done.
| | 02:35 | So controllerDidChangeContent.
We have finished processing those changes.
| | 02:47 | So tableView endUpdates.
So 50% done in the delegate methods.
| | 02:53 | We need two more, and this is all because
we can make different kinds of changes to
| | 02:58 | that NSFetchedResultsController.
| | 03:01 | We might be doing inserts, creating new
objects, that's what we've just done here, but we
| | 03:05 | could also be deleting them or we could be
updating them, or in a long list we could
| | 03:10 | be moving things around if they have
to show up in a particular sequence.
| | 03:15 | So I could just write the code to respond
to the inserts that we're doing and refresh
| | 03:19 | the tableView there, but then we would have
to come back several times to make it work
| | 03:23 | with deletes and changes and so on.
So I'm going to fully configure it now.
| | 03:27 | So the next one that I'm going to create
is also void, and it begins with controller,
| | 03:31 | and it's a much longer one here.
| | 03:33 | We're looking for the didChangeObject:
atIndexPath: forChangeType: newIndexPath.
| | 03:41 | It gives us multiple options, because this is
called multiple times under different circumstances.
| | 03:48 | The first thing that I want to do is grab
hold of the tableView that I'm working with,
| | 03:53 | and rather than call self.tableView all the time,
I'll just create a temporary placeholder for it.
| | 04:04 | The main thing I'm going to ask in this
method is, hey, what kind of change just happened?
| | 04:09 | Because this will be called
automatically when all kinds of changes happen.
| | 04:13 | There will be passing in an
object, hey, this object has changed.
| | 04:16 | Well, what do you mean by that, was it an insert,
was it a delete, was it a change, was it a move?
| | 04:22 | So we get not only the objects being passed
in, but the change type, and I do different
| | 04:27 | things based on the change type.
| | 04:29 | So I'm actually going to
create a switch statement here.
| | 04:32 | And we're going to switch on type.
| | 04:34 | Now, you've seen that I like to
always write out code in full.
| | 04:38 | I'm not a big fan of copying and pasting stuff.
| | 04:41 | I'm going to take a break from that habit
and just paste in a little code here, simply
| | 04:45 | because it would be really tedious for me to type
it all in, certainly tedious for you to watch it.
| | 04:50 | So let me copy in some code here, and I
am just going to explain what this does.
| | 04:55 | So I'm just pasting in those options inside that
switch type, because we're breaking down into four things.
| | 05:03 | If there's an insert, so we have this enumeration we're
checking, what type of change was it? It's an insert.
| | 05:08 | Okay, well, tell the tableView to insert a
particular row at this newIndexPath with a fade.
| | 05:15 | Okay, done. Very similar to that, it's a delete.
Okay, if it's a delete, what do we do?
| | 05:20 | We tell the tableView to update
itself but to delete a particular row.
| | 05:24 | If it's a change, we need to change the
contents of a particular cell, so what I'm doing here
| | 05:30 | is I'm first talking to the fetchedResultsController and
saying, hey, give me the course at your particular index.
| | 05:37 | Then what I can do is grab the current cell at the
current location in the tableView and change its title.
| | 05:44 | Now, in case you don't do a lot of this,
because I'm creating new objects here, I have to wrap
| | 05:48 | this up in curly braces within a
case statement just to make it work.
| | 05:53 | Finally, we have the idea
that something could move.
| | 05:57 | This really wouldn't be a problem for our
example, but if you had a lot of rows being
| | 06:02 | brought back with perhaps a sequence number, you
might change them to change the order around there.
| | 06:08 | So effectively what this is doing is both
a delete the original position and insert
| | 06:12 | at the new position.
| | 06:14 | And that's that method taken care of.
We're updating the individual objects.
| | 06:18 | We have one more delegate
method for fetchedResultsController.
| | 06:21 | Again, void, and again beginning with controller,
and it's this one, didChangeSection this time.
| | 06:29 | Not the individual object, but we changed a
section, and there is a particular kind of change.
| | 06:34 | Really, what we are interested in, too, did
we delete a section or did we add a section?
| | 06:39 | Because what might be happening here is if
we're inserting a new row into an existing
| | 06:44 | section, then this doesn't call, but
because we're automatically splitting sections up
| | 06:49 | by author, that might mean that just by creating new
course object with a author, we need a new section.
| | 06:56 | Or if we delete the last course for a
particular author, we need to delete a section.
| | 07:01 | Now, this method will be called automatically
at the right time. It just needs to know what
| | 07:05 | it's supposed to do.
| | 07:08 | Once more, what's happening is when this is called,
we will have a fetchedResultsChangeType called type.
| | 07:13 | So I'm going to switch on that again, but
it's actually simpler here, I only need to
| | 07:17 | switch on two options.
| | 07:20 | But once again, I'm going to
paste it in just to save some time.
| | 07:23 | These are my two options.
Was there an insert?
| | 07:26 | If there was an insert, tell the tableView to insert
a new section at a particular index and fade that in.
| | 07:34 | If not, there was a delete, in which
case tell the tableView to delete a section.
| | 07:38 | There is a bunch of code here, none of which
is particularly complex, most of which you're
| | 07:43 | going to start kind of putting in your own
personal library, because it's the style of
| | 07:47 | code you would use all the time with Core Data.
| | 07:49 | I'm going to save this, build it,
see if it's giving me any issues.
| | 07:52 | It doesn't look like it, and go
ahead and run the application.
| | 07:57 | So I've got three so far.
| | 07:58 | We're going to see in a moment
how to delete these old ones.
| | 08:01 | I'm going to add a New Course, click into
Title, let's call it Core Data, Author is
| | 08:09 | Simon, I'll leave the date as is or at
least change it a little bit. Why not? 10-16.
| | 08:15 | This has to follow the format year-month-day,
and it should work fine. We'll click Save.
| | 08:22 | And there we go, we're updating, we're adding
a new section automatically, because it didn't
| | 08:27 | have an author with that name.
| | 08:28 | To double-check that, that works correctly,
if I create a New Course here, let's call
| | 08:33 | this Objective-C and use the same Author
name Simon, we shouldn't get a new section, it
| | 08:38 | should appear under the same
section, and indeed it does.
| | 08:42 | However, we've got some messy stuff here.
| | 08:44 | It would be nice if I
could start to tidy this up.
| | 08:46 | And in the next section, we'll see how do
we actually start deleting, because that's a
| | 08:50 | lot easier than anything we've done so far.
| | Collapse this transcript |
| Deleting objects from the table view| 00:00 | So we just added a whole bunch of code to
the Courses Table View Controller to deal
| | 00:05 | with updating the table view in the case
of changes and updates and deletes, so let's
| | 00:10 | have the Delete feature.
| | 00:12 | Now this one is much simpler, and it won't
require a new view controller, and we actually
| | 00:16 | already have all the delegate method in
place to respond to deletions, so the table view
| | 00:20 | should be updated automatically.
| | 00:22 | So everything I need to do is in the CoursesTableViewController.m.
What I've got to do is scan through a little
| | 00:29 | bit because these days by default they're
going to provide a method that's commented
| | 00:34 | out, and this is the one that I want.
| | 00:37 | I can't use the jump bar for this because
it is commented out, but it's commented out
| | 00:41 | right now with Override to
support editing the table view.
| | 00:44 | So I'm going to remove those comments here.
| | 00:47 | This is what we're looking for, the
tableView: commitEditingStyle: forRowAtIndexPath.
| | 00:54 | I am actually going to delete most of it
because I don't need the second else, and I actually
| | 01:00 | don't need the thing that's trying to
change the table view, that's already going to be
| | 01:04 | taking care of by all those
delegate methods that we put in.
| | 01:08 | So this is what I start with, if (editingStyle ==
UITableViewCellEditingStyleDelete),
| | 01:15 | did they delete something?
| | 01:16 | Well, if so, what I've got to do is I got
to grab the managedObjectContext, and I've
| | 01:21 | got to grab the actual course that somebody
is just trying to delete and then tell the
| | 01:25 | context, hey, delete this guy.
| | 01:27 | So what I could combine this all into one or
two lines, let's just break it apart for clarity.
| | 01:35 | So here, the first three lines, 137, grab the
managedObjectContext, and 138, ask the fetchedResultsController,
| | 01:43 | for whatever object is it the path being
passed in, which is passed in to this method,
| | 01:48 | we have that parameter.
| | 01:50 | That will give me the course we're trying
to delete, and then all I do is on the next
| | 01:54 | line just pass that object to context,
say, hey, go and delete this course.
| | 01:59 | Well, if I'm deleting that object, that will
not save those changes. It will not persist
| | 02:05 | them, so I'm going to go ahead
and tell the context to then save.
| | 02:09 | We save the user way first by creating an
error pointer, then we call the save method
| | 02:18 | of the managedObjectContext, which I had
already grabbed as a variable called Context.
| | 02:24 | And again, we're negating that check because
if it works, it returns yes, and we were only
| | 02:28 | interested if it returns no. We'll pop up an
NSLog, but we are assuming and hoping it will work.
| | 02:34 | Let's check it out. Save that and run it.
| | 02:39 | So I have the few courses that I've added.
Let me get rid of the second one. Swipe Delete.
| | 02:44 | Not only do we delete we
delete and update as well.
| | 02:48 | Swipe and Delete, swipe and delete.
And those changes should be persistent.
| | 02:54 | If I quit out of this and go back in and run it
again, all I'd expect is those two entries, looks good.
| | 03:01 | And that's adding
delete to the view controller.
| | Collapse this transcript |
| Creating a display view controller| 00:00 | Next up, we're going to add another
View Controller to the storyboard.
| | 00:04 | This one will be used for both displaying
the details of a particular course and also
| | 00:09 | for editing an existing one.
| | 00:11 | We don't have to do it that way, but we're
going to make this exist in two different modes.
| | 00:16 | And this will actually be a lot easier
than the add screen because we're not going to
| | 00:20 | mess with modal delegation.
| | 00:22 | So I'm just going to zoom out for a
moment, so I get a better view here.
| | 00:26 | Open up my Utilities Panel and from the
Objects library, drag on a regular View Controller.
| | 00:32 | It doesn't really matter where I put it. I'll just
drag this to the side a little bit and zoom back in.
| | 00:43 | Now what I want to have happen is that when
we just tap a normal cell, that we're going
| | 00:50 | to go to the detail view. That's the standard iOS way
of doing things, select it, move to the detail view.
| | 00:56 | So that's all we need to do is create a
segue between the cell and this view controller.
| | 01:00 | Now be careful here, because I'm not going to
Ctrl-drag from the Table View or from the button,
| | 01:05 | I want to make sure the cell is highlighted,
then hold Ctrl down, click and drag over,
| | 01:11 | and we're going to do a push segue, which should
mean that will automatically be embedded inside
| | 01:17 | the navigation controller, and when I select it
we should see the Blue bar up here at the top.
| | 01:22 | Now because naming your segues is a good idea,
we've got multiple segues out of that first
| | 01:28 | table view, I'm going to select it, open up the
Utilities Panel, and give it be Identifier of showDetail.
| | 01:37 | Now we will need a custom view controller
class for this. It's not going to be a lot
| | 01:41 | of code, but we'll need one anyway.
| | 01:43 | So I'll add a New > File,
it's in Objective-C class.
| | 01:49 | Because this will work as both the display and
edit, I will call it DisplayEditViewController.
| | 01:54 | Again, I want to make sure it's a subclass
of UIViewController, not UITableViewController
| | 02:01 | or anything else, and click Next.
| | 02:04 | Create it and then just make sure that in
the storyboard that View Controller is actually
| | 02:11 | hooked up, and I think its identity is the
custom class, not the generic UIViewController.
| | 02:16 | There we go, DisplayEditViewController.
| | 02:20 | Same way that I did before, what I want to be able to do
is pass in a reference to the current course objects.
| | 02:25 | So I'll do that first, jump into the header file for
this new DisplayEditViewController and add a property.
| | 02:38 | It's a little puzzled because I don't have an
import to the Course.header file, so we'll do that now.
| | 02:44 | And that should get rid of my error message.
| | 02:49 | Don't need to synthesize it. The only reason
I would want to do if I wanted to avoid using
| | 02:53 | the Underscore for the
internal instance variable.
| | 02:57 | So jump back into the Storyboard, I need to
add some features to this View Controller,
| | 03:01 | it's a little bit plain for right now.
| | 03:02 | I could create them in
code, but it's easier here.
| | 03:05 | I'm going to define three
text fields and drag them all.
| | 03:08 | Now I'm going to make this
exist in two different states.
| | 03:11 | So the first time around while I'm going to
drag these text fields wider, I'm going to
| | 03:15 | change them so they're not enabled.
| | 03:19 | They are a little close to the top for me
right now, so I'm going to drag them down a bit.
| | 03:25 | With them all selected, I'm going to jump into
the Attributes Inspector, change their Border
| | 03:30 | Style to None, and also come down and find
Enabled and turn that off, because the first
| | 03:37 | view of this is just
viewing them not editing them.
| | 03:43 | I'm now going to switch into Assistant mode,
selecting that display view controller should
| | 03:50 | bring us up the DisplayEditViewController.h
file which is where I want them all, and we'll
| | 03:54 | just grab them one at a time, Ctrl-drag and create
an Outlet, for titleField, authorField and dateField.
| | 04:06 | Let me switch back to standard editor, and
I'm going to jump into the class file for that.
| | 04:14 | So into the implementation here,
and I'm going into viewDidLoad.
| | 04:18 | Well, I'm going to grab the contents of the
course objects that were passed in and just
| | 04:23 | set those to the values of my text fields.
| | 04:31 | So that should take care of populating
those fields when this View Controller appears.
| | 04:42 | But to make sure we actually have a course
object given to this detail view, I need to
| | 04:47 | make sure I'm passing it from our main Table
View Controller when we do the segue to this.
| | 04:52 | So I'm going to jump over into CoursesTableViewController
and find the prepareForSegue, which right now
| | 05:00 | is just segueing to the other view
controller, that's the addCourse one.
| | 05:04 | So I'm going to add a new if statement,
and my new segue was called showDetail.
| | 05:13 | And now I just need to add two or three lines
here to make sure I'm passing in a new object.
| | 05:19 | First, what I need to do is grab a reference
to the View Controller I'm segueing to, and
| | 05:23 | this class doesn't know about it right now,
so let me zoom back up to the top and do an
| | 05:28 | import for it, DisplayEditViewController,
jump back into prepareForSegue, and what we
| | 05:37 | need to do is create an instance of that.
| | 05:46 | Very similar to the first line I did in the previous
segue by getting it from the destinationViewController
| | 05:52 | property, and now we need to find
the correct object to pass that in.
| | 05:57 | I'm going to ask that from the table view
first by grabbing an indexPath and just need
| | 06:02 | indexPathForSelectedRow, because that will
give me the row that someone has just tapped
| | 06:07 | on, and use that to go against the fetchedResultsController,
find the course object that's at that IndexPath,
| | 06:19 | and save it just in a temporary
reference called selectedCourse.
| | 06:24 | Finally, I can use that to just set that currentCourse
property in the View Controller we're about to segue to.
| | 06:33 | Save that, run it.
| | 06:40 | So we should be able to click on one of
these and jump into the details for that course,
| | 06:46 | the title, the author, the release date.
| | 06:48 | We still have our separate segue from the
bottom up here. Let me Cancel out of that,
| | 06:53 | but we can jump in and see the
details of a particular course.
| | 06:56 | It might not be the prettiest
interface in the world, but it works.
| | 07:00 | Next, we'll hit the last big part, changing
this detail view controller so that it also
| | 07:05 | works as an edit screen.
| | Collapse this transcript |
| Switching modes in a view controller| 00:00 | So we currently have this DetailViewController
just showing up un-clickable, just displaying the data.
| | 00:06 | I'm also going to make this work in Edit mode so it
allows us to edit the item that we are currently looking at.
| | 00:12 | So jumping back onto the Storyboard, I am going
to work with this DisplayEditViewController again,
| | 00:20 | open up my Utilities panel, and I want a
couple of buttons I am going to drag on. I can just
| | 00:25 | remind myself where these text fields are
because I currently have them without a boundary.
| | 00:29 | I am going to drag on a couple of buttons
just up at the top here, and just so they don't
| | 00:36 | interfere a little bit, I will
make them a little bit smaller.
| | 00:45 | One says edit, the other says done, I am actually
going to change the done button so that it is hidden
| | 00:52 | and then shift into Assistant View so we
should be looking at the DisplayEditViewController
| | 00:56 | on the right-hand side, and I'm going to drag from
both of these buttons and create actions for both of them.
| | 01:02 | Although I also want outlets as
well, so let's do both of them.
| | 01:06 | So Ctrl-drag from Edit, I will set first an
outlet, call it editButton, and then I will
| | 01:13 | Ctrl-drag, and this time do an action.
And I'll call the Action startEditing.
| | 01:23 | And then from the done button, Ctrl-drag,
first creating an outlet, doesn't really matter
| | 01:28 | where I put it, and then Ctrl-drag and create
an action, doneEditing, and let me just tidy
| | 01:39 | that up a little bit and
put my IBActions together.
| | 01:44 | Now I am sure many of you have already
figured out exactly what I'm doing here, and yes of
| | 01:49 | course, I could make them the same button.
I could just toggle the state of the button.
| | 01:54 | I'll toggle the words on the button. This
would just be a very simple way of doing this.
| | 01:58 | We are going to shift into Editing mode, let
me click edit, and that button will disappear,
| | 02:02 | and the doneButton will appear and then
when we're finished editing, we click done, and
| | 02:05 | we shift back into the normal display mode.
| | 02:08 | So get out of Assistant mode and into the
DisplayEditViewController implementation file,
| | 02:14 | where I should have the
startEditing and doneEditing entries.
| | 02:18 | Now as you know, I don't tend to paste a lot
of code, the only code I will occasionally
| | 02:22 | paste from time to time is where it's
entirely uninteresting, and that's what this code is.
| | 02:27 | I am just going to paste in some.
| | 02:29 | And all we're doing here first is turning on
titleField, authorField, and dateField, because
| | 02:35 | I'd disabled them in the Basic Display mode,
and then changing their borderStyle to actually
| | 02:40 | have the classic rounded rect to be another
visual affordance so that they are editable,
| | 02:45 | I'll then hide the editButton, and I'll
turn on the doneButton, that's that one done.
| | 02:50 | And then in the doneEditing, I have the
flipside of this code that I am just going to paste
| | 02:55 | in here where when we're finished editing,
we will set those again to disabled or rather
| | 03:00 | enabled = NO, turn their borderStyle back Off,
hide the doneButton, and make the editButton visible.
| | 03:07 | Now we got one more block of code here which
is if we have done some edits on those text
| | 03:12 | fields, I want to take those values and put
them back in that object that we currently
| | 03:16 | have that we are holding in
memory of the current course.
| | 03:19 | Because without actually making those updates,
there is going to be nothing for the context to save.
| | 03:23 | So again, that's the very uninteresting code.
There are two lines that are interesting,
| | 03:28 | which is if we are done editing, I need to be
able to tell the managedObjectContext to save.
| | 03:33 | Here is a problem, though, this current DisplayEditViewController,
if I jump across to say the header file for
| | 03:40 | that, I actually don't have a reference to the
managedObjectContext. I can't call save on that.
| | 03:46 | Well, what I could do is create a property
for that and pass it in, but let me show you
| | 03:51 | another way of doing it.
| | 03:52 | Of course, I know that in my AppDelegate that
managedObjectContext is declared as a property,
| | 03:59 | I should be able to get to it.
| | 04:01 | It's a little tedious to go this way if I
wanted to do it multiple times, but if I just
| | 04:05 | want to do it once,
here's how I'd grab hold of it.
| | 04:08 | Jump back into my implementation and after
this line which is where I want to grab hold
| | 04:13 | of the managedObjectContext, what I'll do
is I will create a reference to AppDelegate.
| | 04:17 | At the moment it doesn't know what that is,
so I am going to go ahead and just call Import
| | 04:22 | on it, so it knows about the existence of my
custom AppDelegate class, import AppDelegate.h.
| | 04:29 | And now we should be able to create a
reference to the AppDelegate class. I will just call
| | 04:33 | it myApp and grab hold of that by casting to
an AppDelegate from a call to UIApplication,
| | 04:43 | sharedApplication, although be careful here
because you don't just want the application
| | 04:48 | object, you want the Application object's
Delegate. That's what you are is the AppDelegate.
| | 04:54 | That now gives me a handle to the running
AppDelegate, and it allows me to grab hold
| | 04:59 | of say the managedObjectContext.
| | 05:01 | However, I don't even have to do that
because then I'd create an NSError object and call
| | 05:06 | it, but just jumping back to my AppDelegate, we
haven't really used this much, but AppDelegate
| | 05:12 | itself has a method called saveContext,
which just goes ahead and does that.
| | 05:19 | So why not just call that directly?
| | 05:21 | Jump back into my DisplayEditViewController,
and now I've got a handle to AppDelegate,
| | 05:25 | I just say myApp saveContext.
Save this, build it, no issues, let's run it.
| | 05:35 | So I should be able to click on one of
these courses, shift into Edit mode, change the
| | 05:40 | name, done, and if I click back on Courses, I
should see that new section being automatically added.
| | 05:50 | Click this other one, edit, done, and back and
they should be combined into the same section.
| | 05:59 | And we have that view controller
now existing in two different modes.
| | 06:05 | There was one more thing I wanted to do, which is kind
of tangential, but it's quick, so bear with me here.
| | 06:12 | Currently, when I click the plus button we're
getting this New Course appear with our default
| | 06:16 | values of TITLE in uppercase, AUTHOR in
uppercase, and a fixed Release Date of 2012-01-01.
| | 06:23 | And that's because canceling out of this and
going back into the project, in my data model
| | 06:28 | what I have going on here is Author has its
default value of AUTHOR, title has its default
| | 06:34 | value, and releaseDate has this set string here.
| | 06:38 | It's not very useful, because often what you
want with a date is you want something that
| | 06:42 | moves, and putting in 2012-01-01 is not going to
be all that useful in a month or a year or whatever.
| | 06:49 | So let's say we want this
to default to today's date.
| | 06:52 | Well, how do we do that?
| | 06:53 | Well, there are a couple of shortcuts for
entries you can put in here, but none of them are
| | 06:57 | going to have the effect that we want.
| | 06:59 | What I'd actually like to have is I am
going to clear that one out, and I'm going to do
| | 07:03 | this in code, I am going to have custom behavior
in our course class to provide a default value.
| | 07:08 | So the idea is when this course is actually
instantiated, we will set that value in code
| | 07:15 | if it wasn't capable of doing
it in the actual data modeler.
| | 07:17 | Now the question is, well, where?
Do we just do it in the init method?
| | 07:22 | And no, you don't, because init is not the
best place to put it, because it's not you
| | 07:25 | who is initializing.
So here's what I want it to say.
| | 07:28 | In a custom class that you have, if you
want to add a little bit of custom behavior to
| | 07:32 | deal with initialization, and this of course
means that we are inheriting from NSManagedObject,
| | 07:36 | what I am going to do in here is
create a void method for awakeFromInsert.
| | 07:44 | This is the method that's going to be
called when a managed object is instantiated, and
| | 07:49 | when you want to inject your code into that
process of waking this up after it's being
| | 07:54 | inserted for the first time.
| | 07:56 | Usually what we do is first a super call and
then any custom code that we have, which in
| | 08:01 | our case is simply self.releaseDate=,
and let's do it to the current date.
| | 08:09 | That's it, custom behavior that will now
be joining in the whole Core Data process.
| | 08:14 | So our data model no longer has a default
value for date, but when I run this application,
| | 08:21 | and when we decide to create a new course,
it should be defaulting to today, and I should
| | 08:26 | see that value, there we go, ninth of
September, 2012, and there we go.
| | 08:33 | Yes of course, this app could do with some
finishing touches, particularly with things
| | 08:37 | like the dates and having date pickers, but
we have everything going on, we have fetched
| | 08:41 | results controllers, we have table views
grouped into sections, we have modal segues, we have
| | 08:47 | pushed segues, we have Core Data modeling,
Core Data fetching, Core Data creation, editing,
| | 08:53 | deleting and all of it from
a completely empty project.
| | 08:57 | So if you made it all
the way through, nice job.
| | Collapse this transcript |
|
|
6. Putting It Together: CocoaCreating a Core Data Cocoa app without code| 00:00 | In the next few minutes we are going to take
all the same Core Data ideas, entities, managed
| | 00:05 | object context, saving, fetching, deleting
and put them all together again, but this
| | 00:09 | time in a Cocoa desktop style app.
| | 00:12 | And seeing as we did rather get buried in
code doing this in iOS, this time around we
| | 00:17 | are going to take a different approach.
| | 00:19 | I am going to create a new OS X application,
this is a Cocoa Application, and I'll call
| | 00:25 | this CodelessDemo, because we are going
to do this without writing any Objective-C.
| | 00:30 | This would not be possible in iOS, but we
can do it in Cocoa because of Cocoa bindings.
| | 00:36 | So I'll give that a name, I want to make sure
that Document-Based Application is unchecked,
| | 00:40 | Core Data and ARC are both checked,
and everything else is unchecked.
| | 00:45 | Now of course, in a typical Cocoa App you will
be writing code, but putting this constraint
| | 00:49 | on ourselves, seeing how much can be done
without writing Objective-C will give you
| | 00:54 | a good idea of the power of Cocoa bindings
when used together with Core Data, and I am
| | 00:59 | assuming that you know at
least the basics of Cocoa bindings.
| | 01:02 | If you're coming from a pure iOS background
where they aren't available, I'd recommend
| | 01:06 | you take a look at the relevant sections in
the Cocoa Essential Training course first.
| | 01:11 | So we've got this brand-new application, there
is nothing in my data model, there is nothing
| | 01:16 | on the user interface, I'm going to create another course
entity here with three attributes, title and author that
| | 01:28 | are both strings and releaseDate, that's a date.
I'll create some basic default values for them.
| | 01:39 | And once I make sure that they are defined
and named, I am just going to go up to the
| | 01:43 | Editor menu and create the subclass
for these and add that to the project.
| | 01:50 | I am still not writing any code. Obviously, we do
have Objective-C, I am just not writing any myself.
| | 01:56 | Once that's done, I am going to jump over
to the main Window in our MainMenu.xib file,
| | 02:02 | so clicking the Window to open that up.
| | 02:04 | Now I am going to go over to my Object Library
in my Utilities panel, and from the Data Views
| | 02:10 | section drag on a Table View.
| | 02:17 | Drag it a little bit wider here. I'm leaving
a little room at the bottom because I want
| | 02:22 | the ability to add a couple of buttons.
| | 02:25 | Now if I don't do that now, just jumping to
my controls section, I'll drag on a Gradient
| | 02:30 | button, I actually want this to just have
the plus sign to allow us to add a new entry.
| | 02:37 | So in my Attributes Inspector, I am going
to get rid of the title, and then from the
| | 02:41 | Image section what I am looking for is the
NSAddTemplate, and that will change this to
| | 02:46 | the plus sign down here that looks good,
doesn't need to be that wide, so I'll drag it a bit
| | 02:51 | closer and then just do an
Option-drag to copy that.
| | 02:55 | So I have another button, but this time I'm
going to change that to NSRemoveTemplate,
| | 03:01 | so we have the plus and the minus signs here.
| | 03:04 | Everything's standard Cocoa element so far,
but now what I need is a controller object
| | 03:09 | to handle the communication between this
Table View and our collection of course entities.
| | 03:16 | So what I am going to do is go into my Object
Library again, select from the Objects & Controllers
| | 03:21 | section, what I am looking for
is this guy, Array Controller.
| | 03:25 | This is the same controller I'd use for
doing binding in non-Core Data applications, so
| | 03:30 | if I just wanted to connect a Table View to an array
or a mutable array, but it is also Core Data aware.
| | 03:39 | It's a non-visual object, so I drag it over
here into the dock, it doesn't actually have
| | 03:43 | any kind of appearance on my window.
And now I need to configure that.
| | 03:48 | I'm going to tell it where it's
supposed to get its data from.
| | 03:52 | And what I can do is point it
directly the managed object context.
| | 03:56 | So with this Array Controller selected I'm
going to come up here, and what I'm looking
| | 04:00 | for is my Bindings Inspector which is the
last, but one and usually I'd connect this
| | 04:06 | here to say a Content Array.
| | 04:09 | What I am looking for is the Managed Object
Context section down here, I am going to expand that.
| | 04:14 | And Managed Object Context is an accessible
property of my AppDelegate so I can connect
| | 04:19 | it right there and have this guy act as the
middleman between the data and my user interface.
| | 04:26 | So select to Bind to, from the dropdown,
select App Delegate, and what I'm looking for is the
| | 04:33 | property called Managed Object Context
which will be part of self, so self.
| | 04:37 | It should appear as I start to type--yes, there
we go, managedObjectModel and managedObjectContext,
| | 04:43 | that's the one that I want.
| | 04:44 | However, this is the secret to using this
with Core Data, not just connecting to the
| | 04:49 | Managed Object Context because we could
potentially have lots of different entities here.
| | 04:54 | So what I then do is go over to this
section of the Inspector, the Attributes Inspector,
| | 05:00 | and right now where it's saying, well, I'm
expecting to represent a class of type NSMutableDictionary,
| | 05:06 | I'm going to select that dropdown and say,
no, you are going to go with entity name,
| | 05:11 | Course, so not a class, but an entity.
| | 05:15 | Then we have a couple of checkboxes down here,
one is Prepares Content, not the most obvious
| | 05:20 | one in the world, but for us this basically means as
soon as you load, do a fetch of all of these entities.
| | 05:27 | So that will work. I'll check that.
So now we have this Array Controller configured.
| | 05:32 | It's going to talk to the Managed Object
Context and grab all the course entities as soon as
| | 05:37 | this application loads.
| | 05:39 | But there's nothing connecting
it yet to the user interface.
| | 05:43 | So I am going to do that now.
| | 05:44 | Well, first I might want to rearrange this
Table View a little bit, give myself a bit
| | 05:49 | more room to play with, because it's often
useful to have the expanded view of the dock
| | 05:54 | here, so I can select the Table View
because it's embedded inside of scroll view.
| | 05:58 | So I am going to select Table View and
change its Columns to 3, drag it a little wider so
| | 06:04 | I can grab those, I am going to have the
first one being Title, so I'll drag him wider, and
| | 06:11 | the last one is going to end up being the Date.
So we have got three columns here, that's fine.
| | 06:16 | I am going to select the first column and
in my Attributes Inspector, give it a Table
| | 06:22 | Column heading of Title, we should see that
appearing up at the top, select the second
| | 06:27 | column, usually easier to do over on this
left-hand side in the dock rather than clicking
| | 06:32 | around in the actual interface, but that
just makes our columns a bit more presentable.
| | 06:41 | What I need to do is select the Table View
itself, you might need to expand it, we are
| | 06:45 | looking for the Table View inside the
Scroll View and with that Table View selected, I
| | 06:50 | go over into the Bindings Inspector, and I'm
going to tell it where it gets its Table Content from.
| | 06:58 | So down to the Table Content section, open
that up and what I want to do is select the
| | 07:02 | check box and bind that to the Array Controller.
| | 07:06 | I don't need to say anything else about it,
I don't need to say what part of the Array
| | 07:10 | Controller, I'm basically just saying
bind to everything, all the arranged objects.
| | 07:15 | What I do need to do is then go ahead and
say, well, which column is meant to represent
| | 07:19 | which attribute of those entities.
| | 07:22 | So then I go through the columns again,
selecting the Table Column for Title first and what
| | 07:27 | I want in here is in the Bindings Inspector
again, we open up Value, we are binding to
| | 07:31 | Array Controller, but this time what I'm
interested in is the actual Key Path, what is the attribute
| | 07:37 | I want in this column.
| | 07:39 | Well, as I start typing, I should have the
pop-up, and this will only happen if I have
| | 07:44 | created the custom class, but I can see
NSString Title, select that, save, jump over to the
| | 07:50 | next column, bind that to the Array Controller,
author, again it should be appearing, jump
| | 07:58 | over to the next column, select the check
box, bind that to the Array Controller, and
| | 08:02 | this should be release date.
| | 08:05 | Now if I just go ahead and run this
right now, we shouldn't get any problems.
| | 08:08 | Again, we still haven't written any code,
but of course, it isn't very interesting because
| | 08:13 | we have no data to actually work with.
| | 08:15 | So what I want to do is have these buttons
down here, so when I press the plus button,
| | 08:20 | create a new course and
allow me to configure it.
| | 08:22 | So, quit out of that, and we
need to hook this button up.
| | 08:25 | Now, usually you'd think about creating an
IBAction and writing some code. We don't need to do that.
| | 08:31 | What I can do is Ctrl-drag from the plus button,
over to the Array Controller, let go, and from
| | 08:39 | the pop-up select add.
| | 08:43 | Then from the second button, I'll hold down
the Ctrl key, drag over to Array Controller,
| | 08:48 | come down, and select Remove.
| | 08:49 | Save this, run it, click the plus button,
and I can immediately see what I'm getting
| | 08:56 | is my default values. I
could click that several times.
| | 09:01 | This allows editing by default.
| | 09:03 | Now it looks like what it's bringing in here for
the release date is a full NSDate time string.
| | 09:09 | That's not what we want, so we'll
tidy that up in just a second.
| | 09:13 | Question is, is it's saving this?
Well, let's find out.
| | 09:16 | If I quit out of this application and then run
it again, looks like everything is being loaded.
| | 09:22 | But quitting back, let's tidy up that release
date. What I am going to do over here is click
| | 09:27 | on the actual text cell, I am going to kind
of drill down into that point, and then from
| | 09:32 | my Objects section here--actually
I'll drop that down--I want my Controls.
| | 09:36 | I am going to filter on Date because what
I'm interested in is a Date Formatter, I am
| | 09:42 | just going to drag that on to the text cell
and let go, and that should format it both
| | 09:48 | on the way out and on the way in,
run it again, looks correct.
| | 09:55 | It will allow us to change the date, create new
entries, rearrange the columns, whatever you want.
| | 10:06 | The Delete key also works.
| | 10:08 | If I was going to delete a lot, that's a
little inconvenient, so one last thing that I might
| | 10:13 | do is jump back into the interface, select
the Delete key here, and then find my Attributes
| | 10:19 | Inspector, come down to Key Equivalent, click
into that and hit the Delete key, so now that
| | 10:28 | when we hit the Delete key itself, that should
allow us to delete them when the application is running.
| | 10:33 | Let's just double check that, add a bunch of
them, select one, hit Delete and clear them out.
| | 10:39 | We haven't had to write a single line of code,
we've got our entities modeled, we are adding,
| | 10:44 | we are editing, we are deleting, and it's
all done very, very simply and very, very
| | 10:49 | quickly, and even if your main focus is iOS,
one thing you may want to use this for is
| | 10:54 | for creating simple apps that you could use to
do a bunch of easy preloading and pre-creation
| | 10:59 | of objects into a store file that you
could then take that store file and include with
| | 11:03 | a Core Data iOS application.
And we'll see how to do that a little later.
| | 11:07 | But so far, of course, we are still working
with individual entities, and even though we
| | 11:11 | saw how to model relationships earlier on,
we haven't really done anything with them.
| | 11:16 | So next, let's take our data model
and make it a bit more interesting.
| | Collapse this transcript |
| Creating a drilldown application using multiple entities| 00:00 | Now let's start working with related
data both To-One and To-Many Relationships.
| | 00:05 | I've got a simple project here, and what I've
done in the data model is I've created this
| | 00:11 | Course entity that now just has title and
releaseDate, and I've split Author out into its own entity.
| | 00:17 | It would be fine if all I was interested in
was the author name for each course, but if
| | 00:22 | I also want the phone and the email and the bio,
I don't want to have to repeat that information
| | 00:27 | along with the course every single time.
So I have got these two entities.
| | 00:31 | I haven't created a relationship
between the two, so let's do that now.
| | 00:35 | First, I'm going to create the simple
relationship between Course to Author, so I'll switch back
| | 00:41 | into the normal table style, make sure that
the Course entity is selected, click the plus
| | 00:45 | button, and I am going to call this author, we are
going from Course to Author, each Course has one Author.
| | 00:53 | Now the other way around going from Author
to Course, click the plus button, this time
| | 00:59 | one author could have multiple courses.
| | 01:01 | So I am going to call this courses in plural,
and that will be the name of our property,
| | 01:06 | Destination is Course and seeing as we now
have both of them, I am going to select this
| | 01:11 | as the Inverse relationship.
| | 01:14 | However, while this is selected, what I do
want to do is come over into my Utilities
| | 01:18 | panel and make sure this is checked as the
To-Many Relationship, that will be in your
| | 01:24 | Data Model Inspector section.
| | 01:26 | And that should do it, flipping over into
the Graph View, we can see that we have these
| | 01:31 | represented here. Author to Course is a To-Many
Relationship, Course to Author is a To-One Relationship.
| | 01:38 | And then I am also going to go up to the
Editor and burn off the subclasses for these.
| | 01:44 | I only had Author selected there. I need to
make sure both Author and Course are created,
| | 01:51 | and that will do, so we have got
both Course and Author turning up.
| | 01:55 | Now let me jump over to the user interface.
| | 01:58 | I've done a little bit of work here just to
set it out, but nothing is hooked up yet.
| | 02:02 | I have got a Table View with one column here,
a Table View with two columns here, and two
| | 02:07 | sets of buttons, but nothing is actually
connected anywhere. This is all I've done so far.
| | 02:12 | But what I want to do is have this left-hand
table view show a list of Authors, and this
| | 02:18 | right-hand table view shows a list all
related Courses for any selected Author.
| | 02:24 | So what I am going to need is two Array
Controllers. We are going to need those objects to act
| | 02:28 | as the middleman between our user
interface elements and our data.
| | 02:32 | So from the Object Library, I'll select Objects &
Controllers and drag on two Array Controllers into the dock.
| | 02:40 | Now one issue is that right now these will
both be called Array Controller, and I need
| | 02:44 | to make sure that I'm pointing to the
right one when I start to bind everything.
| | 02:48 | So I am going to select the first
one, we haven't configured it yet.
| | 02:52 | But go up here into my Identity Inspector,
and what I can do down here is give it a Label.
| | 02:58 | So we are in the third part of the Inspector section,
and I'll call this first one AuthorsArrayController.
| | 03:09 | Select the second one, and we'll call that
CoursesArrayController, and this will just
| | 03:14 | make them more recognizable when we are binding.
| | 03:17 | Even though you won't actually see them change
over here on the dock, they will both have that label.
| | 03:22 | So I am going to configure the first
one, the AuthorsArrayController first.
| | 03:27 | Very similar to last time, I am going to go
up to the Bindings Inspector, find the last
| | 03:31 | entry here of the Managed Object Context,
and say yes, I would like to bind this to the
| | 03:36 | App Delegate, self.managedObjectContext, because I just
want this one to go and fetch all the author entities.
| | 03:47 | So after the Managed Object Context is
selected, I jump over here into my Identity Inspector,
| | 03:53 | and change this from Class to Entity Name,
the Entity Name is Author, singular, and I
| | 03:59 | want to select the box that says Prepares
Content, which means go and fetch all the authors
| | 04:04 | when this initially loads.
That's my Array Controller configured.
| | 04:09 | It's going to grab that data. I need to do
it from the other end, too, which is I need
| | 04:12 | to connect my Table View
to the Array Controller.
| | 04:16 | So preferring the expanded view of the
dock here, I am going to select the Table View,
| | 04:20 | make sure the Table View itself
is selected, not the Scroll View,
| | 04:24 | come up to the Binding section, and find the
Content area, which says here we are binding
| | 04:29 | to that Array Controller, that's all I need
to do for this one, but I then need to get
| | 04:33 | into that first column, the only column
inside here, so selecting the column and then tell
| | 04:39 | it that its content is coming from AuthorsArrayController
with a Key Path of name, and it should be
| | 04:47 | popping up as I type in N.
That's that first part configured.
| | 04:53 | Next, I have to get onto the CoursesArrayController
and binding that to the Courses Table View.
| | 04:59 | So I'll select that second Array Controller,
jump into its Bindings, we are going to bind
| | 05:03 | this again to App Delegate,
self.managedObjectContext is the Model Key Path.
| | 05:10 | We want this to be loaded with Course Entity,
so I am going to jump over to the Attributes
| | 05:14 | Inspector and change that over from
a Class to an Entity Name of Course.
| | 05:21 | I actually don't need to check the Prepares
Content thing here because I need to pause
| | 05:25 | for a second, think about
what do I want this to contain?
| | 05:28 | See, this is a little different.
| | 05:29 | I don't want all courses. I'm only
interested in the courses for a particular author.
| | 05:36 | I want this to be a proper drill down ability.
And this is how we do it.
| | 05:42 | I need to add one more binding.
| | 05:43 | So I am making sure that that
CoursesArrayController is selected, I jump to my Binding section
| | 05:49 | and what we are looking for--it's above the
parameters--we are still binding to the Managed
| | 05:53 | Object Context, but I'm interested in this part
that says Content Set, it's a subset of Content.
| | 05:59 | We are basically interested in
the Courses property of the Author.
| | 06:04 | So what I'm going to do is select to bind
to the AuthorsArrayController, and what I'm
| | 06:10 | interested in is the current selection, but
not every part of it. Remember, an author will
| | 06:15 | have a name and a bio and a date of birth and
a phone number but will also have a Courses
| | 06:22 | property, that's what we named the relationship
that will return an array. Well, technically,
| | 06:27 | it will return a set of course objects.
| | 06:30 | So I am fueling this now
with that set of course objects.
| | 06:34 | So again, that's the Array Controller configured,
but we don't have the Table View configured yet.
| | 06:39 | The Array Controller is getting the data,
but there's nothing actually connecting it
| | 06:43 | to the user interface.
| | 06:44 | So, I'll select the Table View, give myself
a bit more room here, and with the expanded
| | 06:49 | view here, I'm selecting the actual Table View
itself, say that it's being fueled from that
| | 06:54 | Array Controller, so Table Content,
select Bind to > CoursesArrayController, Done.
| | 06:59 | Now, I need to go through the two columns.
| | 07:02 | I could do more. I just have two
for the purposes of speed here.
| | 07:06 | So expand that, select the first column that
should represent Title, and I want to select
| | 07:11 | its Value, check, we are binding to
CoursesArrayController, Model Key Path should just be Title.
| | 07:20 | Jump to the second column, bind that to
CoursesArrayController, again, this should only now contain a subset
| | 07:26 | of relevant courses for any selected author,
and its Model Key Path should be release date.
| | 07:33 | And while I am at it here, I'm going to
click into that to find the individual text cell,
| | 07:37 | and because I know this is a date, I am
going to drag on a Date Formatter. You don't have
| | 07:43 | to do this, but I might
as well while I am here.
| | 07:46 | And we want that in the actual cell. I
should be able to see it over here as underneath
| | 07:50 | the text field cell part of this interface.
| | 07:53 | Now the next thing I am going to do is none
of these buttons actually do anything, but
| | 07:57 | we saw last time that I can just hook these
up to the relevant array controllers, so just
| | 08:01 | shrinking that down so I can see it a bit
better, there is our AuthorsArrayController,
| | 08:05 | there is our CoursesArrayController.
| | 08:07 | What I can do from the Author section, Ctrl-drag
from the plus button and select add, Ctrl-drag
| | 08:13 | from the minus button and select remove, and the
same to the CoursesArrayController, Ctrl-drag,
| | 08:20 | add from the plus button and
Ctrl-drag, remove from the minus.
| | 08:26 | So I am going to save that and run it.
| | 08:31 | Nothing very exciting yet because we
have no data, so first I'll add an author.
| | 08:35 | Click the plus button.
It looks like something's being added.
| | 08:39 | So with an author selected,
can we create a course?
| | 08:42 | It looks like we can, Course Title,
Course Title, Course Title. It's editable.
| | 08:51 | I don't have any default value for Release
Date, but I should be able to type something
| | 08:54 | in here and have that accepted.
| | 09:01 | Looks pretty good so far, but
let me create another author.
| | 09:05 | That looks all right, but just quite
quickly what I'm going to do here, select that and
| | 09:09 | create a new course, and then I want
to select back on David, and see his.
| | 09:18 | But it doesn't seem to be updating.
| | 09:21 | So while it allowed us to add in new courses,
it does not seem to be refreshing with the
| | 09:26 | list of David's three courses
the way that I would expect.
| | 09:29 | Now that's because I need one more thing.
| | 09:32 | And what I am going to do next is kind of the secret
sauce of creating a drill down using Cocoa Bindings.
| | 09:37 | This is something many people miss or don't
think can be done without writing a bunch of code.
| | 09:42 | Here's the problem.
| | 09:43 | What's happening is that we are selecting
in this left-hand Table View, but that's not
| | 09:49 | officially changing the selected item in
the underlying Array Controller, and it's the
| | 09:54 | underlying Array Controller that's filtering and
controlling what's being shown on the right-hand side.
| | 09:59 | So it doesn't care what we are selecting
over here, it's not actually updating itself.
| | 10:04 | So what I need to do is go back into the interface here,
select the left-hand Table View, and do one more thing.
| | 10:13 | So with that Table View selected, make
sure I have got it here, I am going to go over
| | 10:17 | to my Bindings Inspector.
| | 10:19 | Now we are already binding this Table View
to the AuthorsArrayController, we have got
| | 10:23 | it connected to the Managed Object Context,
but there is one more property I'm interested in,
| | 10:26 | which is this one: Selection indexes.
| | 10:31 | And what we are trying to do is map whatever
I've selected in the Table View to make sure
| | 10:36 | that's also considered what is selected
in the underlying AuthorsArrayController.
| | 10:41 | So we click that, and the Controller Key I'm
interested in here is not arranged objects,
| | 10:46 | it's called Selection indexes.
It should pop up when I do that.
| | 10:50 | Save that, and that should work.
| | 10:52 | If I run this again, we select Simon, I am
seeing Simon's first course, we select David,
| | 10:58 | we are seeing those three courses.
| | 10:59 | I can create a new author, it should
correctly show no courses for that author, but I can
| | 11:07 | start to add a few.
| | 11:16 | Now we have got this drill down happening,
we are following that relationship, and we
| | 11:20 | haven't written a single line
of Objective-C in this project.
| | 11:24 | But there is a couple of other
things we could start to tidy up here.
| | 11:27 | What happens, for example, to a
course when we delete an author?
| | 11:32 | I can do that, but now I have no access to those
three courses that I had added a little earlier.
| | 11:38 | So what's actually happening there?
| | 11:39 | What happens if, for example, we type in
some kind of value in Release Date, and we do it
| | 11:45 | in somewhere that's not actually expected?
Please provide a valid value.
| | 11:49 | Okay, is there any way of
prompting what that should be?
| | 11:51 | So there are a few things
we could do to tidy this up.
| | 11:55 | So next, we'll start adding some code to kind
of flesh this out and start getting into working
| | 12:00 | with some validation and some delete rules.
| | Collapse this transcript |
| Responding to validation issues| 00:00 | Let's get a little more
involved with validating our data.
| | 00:03 | I'm going to jump into the data model file here.
| | 00:06 | Now, we've seen already that when we're
working with entities, and the attributes of those
| | 00:10 | entities, that we can do things
like provide a default value for them.
| | 00:14 | If we're working with numbers, we can
provide a minimum and maximum value.
| | 00:17 | If we're working with strings, we can have
minimum and maximum length, and even provide
| | 00:22 | a regular expression.
| | 00:23 | So, for example, if I wanted to say that
all new authors should have a name that was at
| | 00:28 | least three characters, I'd select that
attribute, and say this had to be a minimum length of
| | 00:33 | 3, maybe I wanted a maximum
length of 100. That will do.
| | 00:38 | And save that data model file.
| | 00:40 | The question is when are these rules applied,
when is this validated, and also, what happens
| | 00:46 | if I need something a bit
more detailed than this?
| | 00:49 | Let's go ahead and run the application.
I'll run the application. I create a new author.
| | 00:57 | Well, I'm going to create an author called Bo,
and Bo should actually violate those validation
| | 01:03 | policies, because as I said it had
to be at least three characters.
| | 01:06 | Well, it doesn't seem to have a problem with it.
| | 01:08 | Now there's the question,
can I create courses for Bo?
| | 01:11 | It looks like I can.
| | 01:12 | I can jump between those
different author objects.
| | 01:15 | I can even create another author object.
| | 01:18 | Well, it doesn't seem like
the validation had any effect.
| | 01:21 | Let's quit out of the application. I hit Quit.
| | 01:25 | Here I get the error message,
Property/name/Entity/Author is too short.
| | 01:31 | So I am getting that validated.
| | 01:33 | What's happening is that it's only
validating when it's trying to save.
| | 01:37 | And at the moment, the only time it's trying to
save is when the application should terminate method.
| | 01:42 | So, we get the error message and then there's an
option here, do I want to quit anyway? Sure, I do.
| | 01:48 | You might be wondering well,
what happens if I do quit?
| | 01:50 | Well, let me just go, and
run the application again.
| | 01:53 | I can see that I lost everything.
| | 01:56 | All the object changes that I made were not
persisted because we couldn't issue that save.
| | 02:01 | So, here's the thing.
| | 02:03 | The managedObjectContext, which is what's
doing the save, well it will allow managed objects
| | 02:08 | to exist when they break these
rules, all the validation rules.
| | 02:12 | It just won't allow them to be saved.
| | 02:14 | And that's almost certainly what you want,
because you often want a bit of time, so you
| | 02:18 | can create an object and manipulate it
into an acceptable state before you save it.
| | 02:23 | You don't want all these rules
to be applied every millisecond.
| | 02:27 | These validation rules will be applied
when we save, when we terminate right now.
| | 02:32 | But we can also choose to
apply them anytime we want.
| | 02:34 | I'm going to create a little button on this
application so that I can call the save and
| | 02:39 | validate without having to
try and quit the application.
| | 02:42 | So I will just drag a button on here, this
is quick and dirty, Ctrl-drag over to the
| | 02:47 | App Delegate object in my dock because the
App Delegate object, if I let go, will have
| | 02:53 | an action called saveAction where
it will try to validate and save.
| | 02:57 | So let me just change the text on
the button here, Validate and Save.
| | 03:02 | And we'll be able to check it out
while we're running through these examples.
| | 03:09 | Try this one again.
| | 03:11 | And if we create Bo, I can click
the button to just check that. No!
| | 03:16 | Property/name/Entity/Author is too short. Okay.
| | 03:19 | Let's change that to Boo, validate
again, doesn't seem to be a problem.
| | 03:24 | So, we are actually calling that validation.
| | 03:27 | But what if we want to do more custom validation
than these kinds of options we're able to do?
| | 03:32 | Well, we can do that.
| | 03:33 | We can add validation methods to our custom
classes for our entities and have those methods
| | 03:38 | called automatically.
| | 03:39 | So, I'm going to quit out of this,
and back into the data model.
| | 03:43 | Let's say that when I create a new course
entity, I want the releaseDate to be a bit
| | 03:48 | more specific than I can write down here.
| | 03:51 | I'd like to say that when a new release date is
entered, it can't be more than 30 days in the future.
| | 03:56 | Now, I can write that kind of condition in
the data model file, I'll have to write a
| | 04:01 | little bit of code for that.
So this is what I do.
| | 04:03 | I'm going to jump into the custom class for
this entity, which is of course Course.m, and
| | 04:09 | in here, I need a specifically named method.
| | 04:12 | Now, this will look a little weird if it's
the first time you've worked much with the
| | 04:16 | key value coding protocol.
| | 04:18 | But what's actually happening is that while I have a
generated method, it won't pop up automatically, unfortunately.
| | 04:24 | But it will have this format, validate,
and then the name of one of our properties.
| | 04:31 | Now, these all begin with a lowercase letter,
but we need to begin them with an uppercase letter.
| | 04:36 | In my case, I'm going to
say validateReleaseDate.
| | 04:45 | This takes two parameters, a pointer to an
object reference, or an ID pointer, here, which
| | 04:52 | I call value and an error
object, which will be called error.
| | 05:02 | Get myself a bit more
room to view this properly.
| | 05:07 | I could use the same method signature and
have a validate title and validate author.
| | 05:13 | Whenever a new entity is created, we try and
save or validate, these methods will be called,
| | 05:18 | passing in the values that we're trying to use, and
these will work with any of your custom properties.
| | 05:24 | You just need to capitalize the property name
after the word validate, and have these parameters.
| | 05:29 | So, this probably looks a little weird
because what we're getting is an id *, a pointer to
| | 05:35 | an object reference, and a pointer to a
pointer of an NSError so that's why we're using the
| | 05:40 | two asterisks here.
| | 05:42 | The reason for this is so this
method can effectively return two values.
| | 05:47 | Right now, it's just returning a
BOOL, that's the official return type.
| | 05:51 | But we sometimes really want to practically
pass an NSError object back out of it as well.
| | 05:56 | Now, you know that up to this point, when
we've been calling methods like save on the
| | 06:01 | managedObjectContext, or perform fetch, we've been
using that ampersand to pass in the address of an NSError.
| | 06:07 | Well, this is the flip side of that.
We're accepting a pointer to a pointer.
| | 06:12 | So we can actually change it and effectively
pass back the location of that, and someone
| | 06:16 | else can use it later.
| | 06:17 | Double indirection pointers, which is what
we're looking at here, they tend to baffle
| | 06:21 | everybody at first, so let me
just show you the code we need.
| | 06:25 | I've just added some code here.
Let me go through what this is doing.
| | 06:29 | First off, we're just asking,
was there a value passed in?
| | 06:33 | If it was nil, then there's nothing we can do.
Just say return YES.
| | 06:37 | Otherwise, what I'm going to do is convert
that value into an NSDate, and then I've got
| | 06:41 | a little bit of code here that's basically
just calculating and saying is that 30 days
| | 06:46 | forward from today?
| | 06:48 | If it is we're going to
create an NSError object.
| | 06:51 | If you've not created NSError objects
before, they're a little convoluted to make.
| | 06:55 | But that's effectively what we're doing.
| | 06:57 | We're bundling up an NSError with an error
message, and then we're passing back the address
| | 07:02 | of it, and rather we're changing the
address of the error parameter that was passed in,
| | 07:08 | saying if we failed, it will
return NO, otherwise it will return YES.
| | 07:12 | Now let's go ahead and test it.
| | 07:15 | So, I'm going to select any of these authors,
click the Plus button, add a new course, come
| | 07:24 | over to the Release Date.
| | 07:25 | I'm going to add one for today, right
now, which should validate just fine.
| | 07:30 | Not a problem there.
| | 07:34 | And now add one for about 5 or 6 weeks ahead.
Again, it's allowing us to make the change.
| | 07:41 | We can change the objects.
It's only when we try and validate,
| | 07:44 | we've got the problem. Date cannot
be more than 30 days in the future.
| | 07:48 | Okay, I'll change that back to a little
less, validate it again. Not a problem.
| | 07:56 | So, this is how we add custom validation
routines to our code by creating these methods with
| | 08:03 | a specific name and a specific method
signature that matches the name of our properties but
| | 08:09 | contains the word validate ahead of them,
passing in a pointer to an object reference
| | 08:14 | and a pointer to a pointer of an NSError.
| | 08:18 | The code that creates an error is a little
convoluted to write, but you're essentially
| | 08:21 | going to duplicate it in all
your custom validation routine.
| | 08:24 | So it doesn't end up being
that much of a pain in practice.
| | 08:27 | Now, how you attempt to visually handle these errors,
either in iOS or in Cocoa will be entirely up to you.
| | Collapse this transcript |
| Working with relationships and delete rules| 00:00 | So currently, we have these two related sense of
entities, we have authors, and their associated courses.
| | 00:07 | Question might be, what happens to a
course object when I delete an author?
| | 00:12 | Well, the answer is it depends.
| | 00:15 | It all depends on how the
relationship is defined in the data model.
| | 00:19 | So let me quit out of this
and go into the data model.
| | 00:23 | All relationships of course, can
be viewed from two perspectives.
| | 00:26 | I am going to open up my Utilities panel here,
that we have right now Author going to Course
| | 00:33 | and Course going to Author.
| | 00:35 | We can view this relationship from either
perspective, but any relationship that's a
| | 00:39 | To-Many is the more interesting
one here to use as a demonstration.
| | 00:43 | So let's take the
relationship from Author to Course.
| | 00:48 | So a single author can be connecting to multiple
courses, meaning can have a reference to multiple courses.
| | 00:55 | And those courses, in return, back the way
will have a pointer to the author object.
| | 01:00 | And this is what we're interested in, here, is the
delete rule being specified in our relationship.
| | 01:06 | The default delete rule is nullify.
| | 01:08 | What that means is if this author is
connected to a course, and I delete the author, then
| | 01:14 | it'll reach into all the course objects that
have that author as a property and just set
| | 01:18 | that particular property to null.
| | 01:21 | The Author object will disappear,
all the Course objects will be updated.
| | 01:24 | If I click the dropdown, there is another
option called No Action, literally do nothing,
| | 01:31 | don't touch the objects at the
other end of this relationship.
| | 01:34 | So if I delete an author, and I have three
courses with a property pointing to that author,
| | 01:40 | don't touch them at all.
| | 01:41 | This could potentially lead to inconsistencies
in your object graph, and I will admit I've
| | 01:45 | never needed to use this option.
The next one we have is Cascade.
| | 01:49 | Those of you who have worked with Database
Design will probably recognize what this is
| | 01:52 | about to do, meaning if this Author object
references three courses, and I delete the
| | 01:58 | Author object, those course
objects will be deleted too.
| | 02:01 | So be careful with that one.
| | 02:03 | And we also have Deny, meaning if I attempt to
delete an author who is an existing reference
| | 02:09 | in one or more courses, the Managed Object
Context will stop me, and it will only allow
| | 02:14 | me to delete an author if there are no
course objects listing that author as a property.
| | 02:20 | So let's go ahead and try Deny here.
I'm going to save this and go ahead and run it.
| | 02:25 | I am going to select my course here
and just delete myself as an author.
| | 02:31 | Well, seemed to work.
So let's go ahead and try and validate that.
| | 02:35 | Here we have the problem, like validation,
these delete rules are only enforced on a save.
| | 02:42 | So items cannot be deleted from property/courses/
entity/author, not the most useful message in the world,
| | 02:48 | but we know what's going on here.
I'm going to quit out of this.
| | 02:52 | It's going to give me the message.
I'm going to quit anyway.
| | 02:55 | If I open this up and run again,
I have got my courses back.
| | 02:59 | But what it should allow me to do is go ahead,
delete all the courses for a particular author,
| | 03:05 | and then delete that author, and that should
validate just fine, and that would be the Deny rule.
| | 03:12 | Now, one of the issues is, it's completely
possible that right now I have multiple courses
| | 03:17 | that already exist without an author, they
just aren't showing up, because the way I
| | 03:21 | have created this particular user interface
screen, it doesn't have any way of showing
| | 03:25 | us courses that don't have an author because
everything here is all driven from the author.
| | 03:30 | I would need to write another window, or
another user interface element, that just shows all
| | 03:34 | the unfiltered courses from that perspective.
But so far, this is a little clunky.
| | 03:40 | What would be really nice is if we try and
do some operation and then validate that
| | 03:45 | there is a problem here, it may be nice if we
could undo the change that caused that problem,
| | 03:50 | and of course, we'll get into that next.
| | Collapse this transcript |
| Implementing undo and redo functionality| 00:00 | Let's add Undo and Redo support to
this Core Data Cocoa application.
| | 00:05 | Are you watching carefully? Because I'm done.
You see it's already there.
| | 00:10 | Our managed object context, the collection
of managed objects, our scratch pad, our
| | 00:14 | beating heart of Core Data already has an Undo
Manager built right in, and it's already keeping
| | 00:20 | track of everything we're doing when we're
running this application, it understands our
| | 00:25 | data model, it understands our relationships.
| | 00:27 | However, it just doesn't seem like it's working
right now, because I don't have anything available
| | 00:33 | under the Edit menu, and I also don't have
the Command keyboard shortcut working, but
| | 00:39 | this is very simple to interact with.
| | 00:41 | And when we usually don't want to directly
call it ourselves, I'm going to show you how
| | 00:46 | I could if I wanted to.
| | 00:48 | So I'm jumping back into the application,
I'm just going to drag on a simple button
| | 00:52 | onto this interface somewhere, and say Undo,
and all I'm going to do here is shift into
| | 01:00 | a system view and manually call it,
so we're connecting to our AppDelegate.
| | 01:05 | I'm going to Ctrl-click from the button.
| | 01:08 | I want to make sure that I'm doing an action
on an outlet, and I'll just call it manualUndo
| | 01:15 | to create a method here.
| | 01:18 | Switch back to the standard
editor and grab my implementation.
| | 01:26 | So I've got my method signature here for
manualUndo, and this is the entirety of what I'd need
| | 01:32 | to do, self.managedObjectContext undo.
| | 01:40 | The undo method takes no parameters, it
returns void, as we should always be able to call
| | 01:45 | undo repeatedly whether it has something to undo
or not, and it will be exactly the same for redo.
| | 01:52 | I'm just going to go ahead and
run it and prove that this works.
| | 01:56 | Let's click one of the authors, and I
click to create a couple of courses, I change a
| | 02:01 | value, and I delete one of the courses.
| | 02:07 | Go up here to the Undo button, click it once,
the course reappears, click it again, click
| | 02:12 | it again, click it again, you can see the
whole stack being measured until we can click
| | 02:17 | Undo no more and nothing happens.
So redo is exactly the same.
| | 02:22 | There is also a rollback method, which takes
the managed object context back to its last
| | 02:27 | saved state, but Undo and Redo are much more common.
| | 02:31 | Of course we don't usually call undo with a
button, so let me actually get rid of that
| | 02:36 | and do the normal style of calling this.
| | 02:39 | We use the menu item, or the keyboard shortcut,
Command+Z or Command+Zed, whatever pronunciation
| | 02:45 | you prefer for that particular letter.
| | 02:47 | And I saw that when we're running this
application that menu item is grayed out.
| | 02:52 | What we want to do is have this automatically
appear and become enabled when there is something
| | 02:57 | to undo and be grayed out when there isn't.
| | 03:00 | Well, you see 99% of that
behavior is already in place.
| | 03:06 | If I take a look at the Undo and Redo actions
in my menu and open the Connections Inspector
| | 03:12 | I'll see that they are sending actions over to
the first responder, they're hooked up correctly.
| | 03:18 | If I select Undo and take a look at its
attributes, I'll also see that we have the Key Equivalent
| | 03:23 | in place for Command+Z or Command+Zed, and
everything is actually ready to be connected and aware
| | 03:28 | of an Undo Manager in our managed object
context, and the thing is we already have one.
| | 03:34 | If I jump over to the AppDelegate
implementation file, up to the Jump Bar here I already have
| | 03:40 | a method here called windowWillReturnUndoManager,
and all it's doing is returning the Undo Manager
| | 03:47 | that's embedded already in the
managed object context I'm already working.
| | 03:52 | This is provided for us in a
default Cocoa application with Core Data.
| | 03:57 | But if I were to put a log message in this
method we'd see it isn't being called right now.
| | 04:02 | There is one thing missing to connect all
these up properly, and that's over in my XIB,
| | 04:07 | in my main menu, I need to select the
window here, open up my Utilities Panel, and I'm
| | 04:12 | looking for the Connections Inspector, and making
sure that what I have selected is the window object.
| | 04:19 | The delegate outlet of this window should
be connected to my App Delegate so that that
| | 04:25 | method can be called.
| | 04:27 | So if I click from here, drag over to the App
Delegate object and let go, everything is hooked up.
| | 04:34 | See this is needed if you create a Cocoa
application that's not document-based, and it kind of
| | 04:40 | strikes me as an omission, it's one of those
things that might change in future releases of Xcode.
| | 04:45 | So if you have created a new project, make
sure you actually have this problem before
| | 04:49 | you fix it, well that is how to fix it.
| | 04:51 | So I'm just going to save
that, go ahead and run it.
| | 04:55 | First I'll change the value, add a new course,
change another value in there, and go up here,
| | 05:04 | and I can see that I've got the Undo menu,
I can start to back those out, we've got a
| | 05:09 | stack, now Redo has become available as well,
and not only that but you'll find it's fully
| | 05:14 | aware of the relationship. So if I create
a new author, create multiple courses for
| | 05:21 | that author, I can still have that Undo
stack all the way in and out of that just hitting
| | 05:27 | Undo repeatedly, or Command+Shift+Z for Redo.
| | 05:31 | And the managed object context
is keeping track of all of it.
| | 05:35 | Now for Cocoa applications this is
already turned on and provided by default.
| | 05:40 | On iOS projects the managed object context
still has an Undo Manager property, but that
| | 05:45 | property is nil by default,
it's not instantiated.
| | 05:49 | Now mainly that's because undo is not as expected
on iOS, it's turned off by default in an application,
| | 05:57 | because it's usually implemented as the shake
gesture and many users still don't know about
| | 06:01 | that as an option, but you can certainly
instantiate a regular NS Undo Manager for your managed
| | 06:08 | object context in iOS, and just do what we've done here,
arrange to call undo and redo on the managed object context.
| | Collapse this transcript |
|
|
7. Store Types and Model ChangesRevisiting Core Data store types| 00:00 | All the applications we have been making
are saving to a single physical store file.
| | 00:05 | We have seen this file a couple of times.
| | 00:07 | For Cocoa, you can find it by going out to
your home folder, then into Library/Application
| | 00:12 | Support, certainly this is the path
if you are accepting all the defaults.
| | 00:17 | And depending on what you've named that
application you'll find an entry, and for me it's under
| | 00:21 | com.mycompany, you'll find an entry
there for the actual saved store file.
| | 00:28 | Now one thing to be aware of is if you've
used multiple projects with exactly the same
| | 00:33 | name, and same details, for example, a lot of
copied projects, say several of the exercise
| | 00:38 | files, which are just the same project in
various saved states, that they'll all be
| | 00:42 | trying to access the same file path.
That may or may not be what you want.
| | 00:47 | Now for iOS applications, it is also
possible to navigate to the equivalent location for
| | 00:53 | the simulator, although it's a little more
tedious to do because the path is not so obvious.
| | 00:59 | But again, still in my home folder /Library/Application
Support, I actually have an entry here for iPhone Simulator.
| | 01:06 | Now in that you may have multiple folders
depending on which simulators you have support
| | 01:12 | for and what you've deployed to.
| | 01:14 | I have been using 5.1 for my applications
and under that I have got an Applications
| | 01:19 | folder, inside that I may have one more or
multiple folders with the long generated name.
| | 01:26 | Now I only had a couple of
applications installed in my Simulator.
| | 01:30 | So I only have a couple of folders.
| | 01:31 | If you have a lot of apps in the
Simulator you'll have folders for each of them.
| | 01:35 | So you'll either need to take a browse through
them or you can look by, say, the Date Modified
| | 01:40 | and have a clue which one
was changed the most recently.
| | 01:44 | But inside a folder for the application here
you'll find the app, you'll find the Documents
| | 01:48 | folder there, and that is the Core Data
store file, in this case, an SQLite format.
| | 01:55 | We've touched on the idea to by default these
files are internally using XML if you create
| | 01:59 | a Cocoa app, or SQLite if you have an iOS app,
but that can be changed, and there are a couple
| | 02:06 | of options for the internal structure of
this file, so let's talk about that now.
| | 02:12 | These are your persistent store types,
XML, SQLite, Binary, and In-Memory.
| | 02:18 | XML is the default option for Cocoa, and
the best thing about it is that it is possible
| | 02:23 | to just open it up and read it, it's XML,
it's human-readable, it might not be anybody's
| | 02:28 | idea of fun, but it is readable.
| | 02:31 | Now this option is not available on iOS, you
don't really have access to the file system
| | 02:36 | in any meaningful way so having a
readable file is not an advantage.
| | 02:40 | XML is what's called an atomic store type.
| | 02:44 | This does not refer to the usual atomic, non-atomic
option of properties in iOS, we're just talking
| | 02:50 | more about the idea of what that means in
the larger computing programming sense that
| | 02:55 | an atomic store file is
taken as one complete piece.
| | 02:59 | The impact of this means that any change to
this file requires the entire file to be written,
| | 03:05 | it is atomic, it's treated as one thing.
| | 03:07 | That's not a problem with the small file,
but it would rapidly become a pain if this
| | 03:11 | is very large, and the tiniest change to an
object require the entire thing to be written in full.
| | 03:18 | So the most powerful and often the best
option is to use SQLite this is available for both,
| | 03:25 | and it is the default storage format on iOS.
| | 03:28 | We could switch our store type to using
SQLite in Cocoa, we'll do that in just a moment.
| | 03:33 | SQLite is fast, but it's also non-atomic,
and what I mean by that is you can make a
| | 03:38 | change to it without rewriting the entire data file,
which makes it a lot faster particular for large stores.
| | 03:45 | Now one important thing you do not need to know
SQLite, you do not use SQLite to calls, to talk to this.
| | 03:51 | You do not run your own SQL statements on
this, you don't import your own SQLite table
| | 03:56 | into this, you don't touch it,
you let Core Data touch it.
| | 04:00 | The internal structure of this is private,
it's not meant to be manipulated by you.
| | 04:05 | And we have Binary, it's another file format,
it is a little faster than XML, although not
| | 04:10 | human-readable, and it's
very fast for small files.
| | 04:13 | But again, it's atomic, it has to be taken
as one thing so any change requires a full
| | 04:17 | rewrite of this file.
| | 04:19 | And finally, we have In-Memory.
This is not a common one but can be useful.
| | 04:25 | What it means is these objects only exist
in memory, it gives you all the Core Data
| | 04:29 | features, but no persistence.
| | 04:32 | That might sound like an odd thing to ask
for, but imagine that you are writing an app
| | 04:35 | that requires up to the moment financial
information, or worldwide flight information, or weather
| | 04:40 | info as it happens.
| | 04:42 | Your app might be getting that
all by calling a web service.
| | 04:45 | So you're bringing the information in to your
application, into your managed object context,
| | 04:51 | you have your data model, you have relationships,
you just don't want to persist any, you don't
| | 04:55 | want to save that information.
| | 04:57 | So you're accessing all the benefits of
loading into Core Data but without saving it.
| | 05:03 | This is a more unusual option, but it is an
option, and it is also possible to write your
| | 05:08 | own custom store type, but that's way beyond
the needs of this course, and also Apple are
| | 05:13 | introducing the idea of using iCloud with
Core Data, again something beyond the scope
| | 05:19 | of this course, something that you really
wouldn't use without one of these existing
| | 05:22 | regular persistence mechanisms anyway.
| | 05:24 | And the most, the most flexible store type
is SQLite, but you may need to convert one
| | 05:31 | store type to another.
| | 05:32 | Say, taking a Cocoa application that started
with XML and converting it to use SQLite instead.
| | 05:38 | We'll see how to do that next.
| | 05:43 |
| | Collapse this transcript |
| Converting store types| 00:00 | I have this new, although straightforward,
Cocoa application I've just written and started
| | 00:05 | to use adding some simple objects to it.
| | 00:07 | Now I have accepted the default store type
that Xcode has provided for this application,
| | 00:12 | which of course, would be
XML because it's a Cocoa app.
| | 00:15 | If I wanted to check then I would look in
the AppDelegate file in the method that creates
| | 00:20 | the persistentStoreCoordinator.
| | 00:23 | Because it's here that it's looking for the
file address, making sure that something is
| | 00:27 | there, creating a directory, if it's not,
and this is the part we are really interested
| | 00:32 | in is adding the persistent
store with the type, NSXMLSStoreType.
| | 00:37 | Now if I had not started to actually use this
application and add data to the store, I could
| | 00:42 | just go ahead and change the store type to
one of the other, say NSSQLiteStoreType, and
| | 00:50 | go ahead and run that,
we're going to have a problem.
| | 00:54 | The file couldn't be open
because it isn't in the correct format.
| | 00:57 | Well, that kind of makes sense.
| | 01:00 | Now seeing as I'd only
created three simple objects.
| | 01:05 | It wouldn't really be a big deal for me to
just delete the store file and let it re-create,
| | 01:09 | and that would absolutely work, but let's
imagine there is a little bit more data than
| | 01:14 | I would want to delete and then re-create.
So we'll talk about how to convert it.
| | 01:18 | I'm going to undo the changes that I made here.
| | 01:21 | I have to change this back to the XMLStoreType
so that we can do a proper migration of the data.
| | 01:27 | Now when we talk about migrating in
Core Data, it really has two meanings.
| | 01:31 | One is to be able to convert from one store
type to another--like XML to SQLite or SQLite
| | 01:37 | to Binary--and it can also mean to convert
from one data model to another. If your store
| | 01:42 | was created with a different data model.
| | 01:45 | We are talking about doing the conversion here,
changing the store from XML format to a SQLite format.
| | 01:51 | So I'm going to migrate it.
| | 01:53 | I have to leave these lines of code here
because this is what's actually grabbing hold of the
| | 01:58 | existing XMLStoreType.
| | 02:00 | So everything I do is
actually going to be after that.
| | 02:03 | So it's way towards the bottom of
this persistentStoreCoordinator method.
| | 02:08 | I'm going to put in some temporary code.
| | 02:10 | I don't want to add a migration feature to
this application, I just want to run some
| | 02:14 | code once to move it over and then
start using the new SQLite version.
| | 02:19 | So here's how I do it as an
ad-hoc, one-off conversion.
| | 02:25 | First, I'll create a new NSURL
object to hold the new path that I want.
| | 02:29 | I am not going to try and replace
the existing one, it's a store data.
| | 02:33 | I am going to make a new
file, which I'll call SQLData.
| | 02:39 | The name doesn't really matter,
you can call it whatever you want.
| | 02:42 | You just need to able to re-create
it when we are looking for it later.
| | 02:46 | Next, I am going to add a line that will let me grab
the old xmlStore from the persistentStoreCoordinator,
| | 02:54 | this is what we are actually
creating on lines 85, 86, and so on.
| | 03:02 | And now I can tell the
persistentStoreCoordinator to migrate from one to the other.
| | 03:06 | So I am calling the migratePersistentStore:
toURL: options withType error.
| | 03:14 | When I'm really interested in the same we
are migrating the old xmlStore that I created
| | 03:18 | a handle on line 92 to the new URL with the
new type of NSSQLiteStoreType, that should do it.
| | 03:29 | Now just a simple log message.
| | 03:34 | That seems like it should be something I should be
able to do without first loading the xmlStoreType
| | 03:38 | but I can't, I have to load it, then convert it.
So I am going to go ahead, run this once.
| | 03:44 | I can see the message popup in the background
that this has migrated, so we at least hit that code.
| | 03:49 | And then importantly I'm going to quit,
because the code that existed was then both loading
| | 03:54 | in the XML version and creating a new SQL
version, I want to go back to it and basically
| | 03:59 | change everything around.
| | 04:00 | I can now get rid of this code, or if I
wanted to keep it around for later just comment it
| | 04:05 | out with the Command+Forward Slash.
| | 04:08 | And then I'm just going to change the
existing code that we had here to bring in the old
| | 04:13 | XML version and change that to sqldata,
that's the new path, but also importantly make sure
| | 04:19 | that it's not trying to load
as XML, but as NSSQLiteStoreType.
| | 04:25 | And just if I wanted to double-check even
before I ran this I could jump to my Finder,
| | 04:29 | go out to the lucky old Application Support, find
my ConvertedDemo where I should see two files there.
| | 04:38 | The storedata one, which is in the
lightweight XML format, and the sqldata one, which has
| | 04:42 | a little bit more weight, just because we're
really at sqldatabase, so it's 20 K over 2,
| | 04:48 | although that difference won't be
as big once we start growing this.
| | 04:52 | That should be in, I am
going to save this and run it.
| | 04:55 | It looks like we can add new options here.
| | 05:03 | Quit out of the application, open it up again,
and we're keeping and persisting all that
| | 05:08 | data, successfully using the new store type.
| | 05:12 | Now if I look back on my Finder window I
still do have the old one hanging around.
| | 05:17 | It's not really important, I could get rid of
it, if I wanted to, I don't need it anymore,
| | 05:21 | but I also have access to the new SQLite
version, could even take the store file over to an
| | 05:27 | iOS application as long as we
had an exactly matching data model.
| | 05:30 | In fact, let's see that next.
| | Collapse this transcript |
| Preloading default data| 00:00 | Earlier in the course we created this iOS
application for editing these course entities.
| | 00:06 | We'll click the Plus button, and fill
out some data, save it, there we go.
| | 00:10 | But what if we'd wanted this
application to be preloaded with data?
| | 00:14 | So the first time anyone opens the application,
it's got a bunch of courses already inside.
| | 00:19 | Well, I can't just go through and create
several entries and be done with that, because the
| | 00:25 | store part of this, the actual file that's
being saved is not part of the project assets,
| | 00:30 | it's stored purely at a user level.
| | 00:32 | So I could create a hundred courses in here,
but as soon as I install this program on another
| | 00:37 | device, I'd have nothing.
We have a few options for preloading data.
| | 00:41 | One, we could write some one-time only code to
detect if it's the first time the application
| | 00:46 | runs, instantiate a bunch of objects, and
immediately save them to the new store file.
| | 00:51 | Now this is possible, but it's going to
interrupt that initial startup, and you'd only ever
| | 00:55 | want to do this for small, or
dare I say tiny, amounts of data.
| | 01:00 | Now similar idea is you might include a Plist
or CSV file as a resource with your project
| | 01:06 | and then write some one-time only code to iterate
through and instantiate all your objects based on those.
| | 01:12 | Again, not something you'd want to do with a
lot of data because it would slow down your
| | 01:17 | app, particularly on that all-important
first time that the user tries it.
| | 01:21 | Now what's a better option is that we
provide a Core Data store file already loaded with
| | 01:27 | data as an application resource.
| | 01:29 | And when we first run the application if we
find there is no store file already there,
| | 01:34 | instead of just making an empty one, we'll copy
that asset across and make it the local store.
| | 01:40 | That of course, must match the store type and the
data model defined in the project, so let's do that.
| | 01:47 | So how do I get that store file?
Well, we could do this a couple of ways.
| | 01:50 | One I could actually use the iOS app here,
painstakingly adding a bunch of new courses,
| | 01:55 | and then navigate out to the file path of the
Simulator and grab the store file that it saved.
| | 02:01 | That's kind of a little tedious,
so let's not bother with that one.
| | 02:05 | Well, another option that I might use is
just make a simple Cocoa application, like the
| | 02:10 | one I am looking at here from the previous
exercise just using Cocoa bindings, and use
| | 02:15 | this to do a front load of data as it's much
easier to type multiple entries into a Cocoa
| | 02:20 | app rather than in an iOS Simulator, or even you
could create a small importer helper application
| | 02:27 | if we're bringing all the
data in from somewhere else.
| | 02:30 | And that means I wouldn't have to have that
import code sitting in my main application.
| | 02:34 | But importantly, whatever is going to create
the store must use the same data model and
| | 02:39 | the same store type.
If there is any mismatch this won't work.
| | 02:43 | So the best idea is you've already configured
the data model correctly in your main application,
| | 02:48 | and you'll just copy that data
model into your helper application.
| | 02:51 | I've got a bunch of entries that I created
here, and I had gone out to the Finder to
| | 02:57 | find the actual entry for this, again, of
course, this has to be in SQLite if we are bringing
| | 03:01 | into an iOS, SQLite application.
| | 03:04 | But I had the ConvertedDemo.sqldata format,
which I have just dragged onto my Desktop there.
| | 03:10 | That's provided in the exercise files if you
want it, and there is just a bunch of entries here.
| | 03:15 | But I don't need the app,
I just need the store file.
| | 03:20 | So what I'll do is I'll go over to the
application that I want to preload a bunch of data and
| | 03:27 | then navigate to where that SQL data file is.
I'm going to drag it into that application
| | 03:33 | so I can have it as a resource.
I'll drop it into Supporting Files.
| | 03:39 | When the window pops up, yes, I want to copy
them into the Destination Group folder, absolutely,
| | 03:44 | and importantly, I want to make sure this is
added to the target, it is a part of our deployed
| | 03:49 | application, Finish.
So this file is called ConverterDemo.sqldata.
| | 03:55 | It doesn't really matter what it's called,
we just need to remember what that name is
| | 04:00 | so that we can access it in
the code in just a moment.
| | 04:03 | So here's what I need to do.
| | 04:04 | I am going to go into my AppDelegate here, and
I'm interested in the persistentStoreCoordinator
| | 04:10 | method, that's going to have everything that
takes care of loading it, dropdown into the
| | 04:15 | actual method rather in the Property,
and give myself a bit more room.
| | 04:20 | So right now what it's looking for
is a file called CDCourses.sqlite.
| | 04:24 | Well, I know that what I want us to look for
a file called ConverterDemo.sqldata, if that
| | 04:31 | file doesn't exist on the file system for
this iOS app then we are going to copy it across.
| | 04:36 | So I am basically going to ignore that path
and just replace it with a new file that I want.
| | 04:47 | So creating a *storeURL, and then we can
ask if that actually exists on the local file
| | 04:51 | system of the iOS device, whether
that's the Simulator or real device.
| | 04:56 | So on line 108 I grab the default file
manager so we can ask if certain files exist, and
| | 05:05 | that's what I'm doing on 109, does that file
already exist with the name ConverterDemo.sqldata?
| | 05:11 | Because if it doesn't exist, we know that
this is the first time through that we've
| | 05:15 | run this application, or if it's not the first
time someone has at least deleted the application
| | 05:20 | off that device or off the Simulator.
| | 05:27 | So then I'll create a new NSURL this just
grabbing the location of that embedded resource
| | 05:32 | on the machine, this is what we used to grab hold
of it, ConverterDemo with the Extension sqldata.
| | 05:43 | And as long as something exists from that
let's copy it across and do copyItemAtURL,
| | 05:51 | the location of that resource, and copy it
to *storeURL, the address that we want it to
| | 05:58 | be at, and I am just going
to provide NULL for the error.
| | 06:03 | Save that, go ahead and run it, and there we
go, the information automatically preloaded.
| | 06:13 | Now the only thing to be aware of, here, is
if you had brought in a file that actually
| | 06:17 | had the same name as the application,
and you'd already been working with it.
| | 06:21 | You might need to clear everything out on the
Simulator so that we load it correctly the first time.
| | 06:27 | So the easiest way there would be either delete
the app from the Simulator or do a reset content
| | 06:32 | in settings and redeploy it, and then
we'll have no store, we'll copy it across.
| | 06:37 | And next time we run the application it will
detect that that store file already exists,
| | 06:42 | so we won't need to copy the asset
this now is our store for this application.
| | Collapse this transcript |
| Dealing with schema changes| 00:00 | Something you may well have run into already
in your experimentation with Core Data, what
| | 00:05 | happens if you change your data model after
you've begun to actually use that data model
| | 00:11 | in a running application?
| | 00:12 | So here I have this simple Courses
application I have created, a bunch of objects here.
| | 00:17 | And let's say now I decide
that my data model needs a tweak.
| | 00:20 | We need a bit more information.
| | 00:22 | So I'll go into the Data Model file and just
add a simple new attribute, I'll call it category
| | 00:28 | and make it a String.
Save that and run the application again.
| | 00:34 | We are immediately going to run into a
problem, basically the thing will blow up.
| | 00:40 | It will give me a whole bunch of information,
but the thing I'm most interested in is the reason.
| | 00:46 | The model used to open the store is
incompatible with the one used to create the store.
| | 00:51 | That's a pretty clear error message,
and it shouldn't really surprise us.
| | 00:54 | We are changing the basic format of how this
information has saved, which means we are changing how it's loaded.
| | 01:01 | It's the equivalent of changing a relational
database schema, or a file format, in an existing application.
| | 01:07 | You're going to run into
conflicts if you do this.
| | 01:10 | So I'm going to stop this and
go back into the application.
| | 01:13 | The question is did we
irrevocably break everything?
| | 01:16 | Well, let's see what happens if I go and back
out that change, remove that attribute, save
| | 01:22 | this again, and run it.
| | 01:25 | We do seem okay, so we've recovered from that
one, but it's not really a good idea to start
| | 01:30 | changing the data model with the hope
that you can undo your changes exactly.
| | 01:34 | I might have played around with this, but
I realized maybe that my new data model is
| | 01:38 | more important than the handful
of test objects I have made so far.
| | 01:42 | Well, of course, I could just go and delete
the store file and start again, either resetting
| | 01:47 | the simulator or clicking and
holding to delete the application.
| | 01:51 | You can try and find the store file and
delete it yourself, but that would be much easier
| | 01:55 | to do with Cocoa Applications than on iOS.
| | 01:57 | And because it's a bit more difficult on iOS,
you will find that there is a little commented
| | 02:02 | out code in the persistent store method that
can help you, because it's very common just
| | 02:08 | to need to delete the store when you're working
with an iOS app just while you're in early development.
| | 02:14 | I'm not going to do that right now, but
I'll show you where it is if you wanted it.
| | 02:17 | So, jump to the persistentStoreCoordinator
method of this iOS Core Data app, and you
| | 02:23 | will find some code commented out here.
| | 02:25 | Now, all this code is in the area that
would be called if there's an error, if there's
| | 02:30 | a problem loading, or
creating that persistent store.
| | 02:34 | And here is the option that I'm talking
about here just a little reminder there.
| | 02:38 | If you encounter schema incompatibility
errors during development, where you don't really
| | 02:42 | care about what's in the store file, you can
just delete the existing store, so you should
| | 02:47 | grab that line of code and put
it in an uncommented out section.
| | 02:53 | Now bear in mind, that you don't want to run
this line every time the application loads
| | 02:56 | because that would be a real problem,
you just continually delete the store.
| | 03:00 | So I could paste it in here meaning that
we'd only hit this brace if we actually have a
| | 03:06 | problem if there's an error that's caused.
| | 03:09 | Though the way I have done it here, we
would actually throw an error so it would abort,
| | 03:12 | so the first time I'd run it, and there was
a mismatch, it would delete the store, I'd
| | 03:16 | need to quit and run it again to
take the new store into effect.
| | 03:20 | But I'm not going to do that right now.
| | 03:22 | But while developing, it can be a
useful piece of code to know that it's there.
| | 03:26 | If you want to make any significant changes
while in the early stages of development just
| | 03:30 | be prepared to throw your store file away,
and if the data is minimal, or it can be rebuilt,
| | 03:36 | that is by far the best option.
| | 03:39 | But at some point, we're going to need to
make a change to a data model that's in use,
| | 03:43 | and we won't want to throw all the data away.
So we're going to see how to do that next.
| | 03:48 | Instead of just changing the data model,
we are going to find out where you can create
| | 03:52 | multiple versions of our data model, and then
describe to Core Data how it should migrate
| | 03:58 | data across those versions.
| | Collapse this transcript |
| Exploring lightweight migration| 00:00 | When you have a data model that's in active
use, meaning that you're creating and saving
| | 00:05 | data based on that data model, and you can't
get rid of that data, but you need to make
| | 00:10 | a change to the model,
| | 00:11 | we do that by creating a new version of that
data model, not just by changing the old one
| | 00:16 | and hoping for the best.
| | 00:17 | We need the old version to still exist so
that we can describe to Core Data how it should
| | 00:22 | convert the old data across to the new format.
| | 00:26 | So let's say we go back to this idea of
wanting to add a new attribute to an existing entity.
| | 00:32 | Well, rather than just add it in here to my
Attributes list, what I'm going to do--with
| | 00:37 | the datamodel file selected and open--
I'll go up to the Editor, and we have an option
| | 00:42 | here to Add a Model Version.
| | 00:45 | So in here it's asking me,
what do we want to call it?
| | 00:47 | Version name, CDCourses 2, Based
on the original model, CDCourses.
| | 00:51 | That's fine, the numbering system is
actually whatever is meaningful to you because Core
| | 00:57 | Data will use its own internal numbering system
based on some hashes inside, but this will do.
| | 01:02 | I'll click Finish, and if you notice over
here that what it's actually done is expand
| | 01:07 | our regular xcdatamodeld file.
| | 01:09 | And really tell us what's going on a little
bit, which is under the hood, it's actually
| | 01:14 | a group, and there is a
couple of files inside here.
| | 01:17 | So we have the new version 2
data model and the old data model.
| | 01:21 | Obviously, both of them are
exactly the same right now.
| | 01:24 | The old one currently has a check mark on it,
and that's saying that this is the active one.
| | 01:30 | So, for example, you may want to spend some
time working on this new version of the data
| | 01:34 | model before you actually
say now it should take effect.
| | 01:38 | What happens to change that little check box,
a lot of people would imagine that it's maybe
| | 01:42 | a right-click button, but it's not.
| | 01:44 | You take the top level element, the part of
the group, and you'll find that in the File
| | 01:49 | Inspector for that top level element
you have a little Current dropdown box.
| | 01:54 | It is a version of data model, what is
the one that we're interested in using.
| | 01:59 | So we'll change to the
second one in just a moment.
| | 02:02 | So let's say we select
the second data model here.
| | 02:05 | Again, being careful which one you're changing,
and then I'm going to add this new attribute.
| | 02:09 | Again, called Category, and
this is going to be a String.
| | 02:15 | I could even provide, say, a
bit of default data for this.
| | 02:18 | Let's say the Default Value is Developer.
| | 02:22 | So what I can then do is come to the main
data model, so selecting the top level element
| | 02:27 | and say that should now take effect.
We should now be using the second one.
| | 02:31 | And you might be thinking, okay is that it?
| | 02:33 | Now we got the check box on version 2,
version 2 seems to have category.
| | 02:38 | Let's go ahead and run it.
| | 02:41 | Well, we still get that same immediate
explosion just because we made a different version of
| | 02:49 | the data model doesn't mean that we
didn't change it, we still changed it.
| | 02:53 | I'm getting exactly the same reason here.
| | 02:55 | The model used to open the store is
incompatible with the one used to create the store.
| | 03:00 | But we might be thinking these
changes that I did weren't that big.
| | 03:04 | If I quit out of this and just go back and
look at them surely Core Data can figure out
| | 03:10 | some simple changes like
adding an attribute to an entity.
| | 03:14 | Well, actually it can.
| | 03:16 | We can turn on something called
Lightweight Migration, and that's us asking Core Data
| | 03:21 | to attempt to infer how the old
version matches to this new version.
| | 03:26 | A Lightweight Migration is a phrase almost
belittle as what this is capable of, it's very, very good.
| | 03:31 | It will let us do things like add and remove
attributes, add, remove, and rename entities
| | 03:37 | in our new version of the model.
| | 03:39 | It will also let you add, delete, and rename
relationships, change the relationship type
| | 03:43 | from To-One to To-Many, and it
will migrate all of this automatically.
| | 03:48 | Although if your User Interface reference
is something that you've removed, or renamed,
| | 03:52 | you will not surprisingly
need to make some changes.
| | 03:55 | And if you've generated a custom class from
your entity you'll need to either regenerate
| | 04:00 | it, or edit it, to bring it in line
with the new entity definition.
| | 04:04 | Here's how we do Lightweight Migration, it's
an option when we open the persistent store.
| | 04:09 | So I'm going to leave that version 2 selected,
we want that, and then I'm going to navigate
| | 04:15 | to my AppDelegate into the implementation
file and find the place where we're actually
| | 04:19 | loading it in, which of course will be
the persistentStoreCoordinator method.
| | 04:24 | We don't typically have to do much with the
persistentStoreCoordinator, only when we were
| | 04:29 | trying to effect what's happening when we
were opening up a store or closing it, changing
| | 04:33 | something about it what we need to get involved.
| | 04:36 | In this example, line 119 is where we were
actually adding this store to our application.
| | 04:41 | We were saying it's a
SQLiteStoreType, and this is where we create it.
| | 04:45 | If there's any errors they
will be thrown right here.
| | 04:49 | I'm showing you this because the secret is
in this part, where we're attempting to add
| | 04:54 | and open this persistent store
to connect it to our application.
| | 04:59 | We have an argument called options that
takes a dictionary, and we can pass in a couple
| | 05:04 | of options to that dictionary to say, hey
Core Data, I would like you to attempt to
| | 05:09 | map that new data across.
| | 05:12 | So before this is getting called up
here I will create a new dictionary.
| | 05:18 | Seeing as it is late 2012, we could use the
new NSDictionary literal format, but if you're
| | 05:23 | not used to it I'm just going to use
the conventional way of doing it.
| | 05:27 | Use the dictionary with objects and keys
initializer, I only need to provide a couple of Boolean
| | 05:34 | representations here, look a little odd.
| | 05:37 | The option I'm looking for that should pop up is
the NSMigratePersistentStoresAutomaticallyOption,
| | 05:43 | long-winded, but it sounds tempting and
then another NSNumber wrapping a Bool here.
| | 05:51 | And the second option begins with NSInfer that's
the one we want, NSInferMappingModeAutomaticallyOption,
| | 06:01 | and then Comma, and nil to close that dictionary.
| | 06:04 | Now we have a dictionary called
options with those two values in it.
| | 06:08 | I'm going to change here to
paste in that option there.
| | 06:11 | Save it and run it.
| | 06:13 | Now I haven't changed the model back so we're still
attempting to use version 2, there we go. It works.
| | 06:20 | We can go ahead and create
new options here, and save them.
| | 06:27 | We're correctly using the new data model.
| | 06:30 | And of course, it would be our job now to
create new User Interface elements that allows
| | 06:35 | me to change that new attribute that
we've added and whatever else is needed.
| | 06:40 | But Lightweight Migration is great for letting
us change our model without losing all our data.
| | 06:46 | However, when Core Data cannot infer the
mapping automatically, when there's been too many
| | 06:51 | complex changes between versions,
then yes it does get more complex.
| | 06:56 | You can provide what's called a mapping model,
this is actually a new kind of file for a
| | 07:01 | Core Data application.
| | 07:02 | I can add a new file and under the Core Data
section where we've got Data Model, we know
| | 07:07 | those, we've got ManagedObject subclasses, we've done
those, and there's a new one called a Mapping Model.
| | 07:12 | That allows you to pick a source, and a
destination mapping model, and then do a bit more manual
| | 07:19 | work about how one should map to the other.
| | 07:22 | Then there's an object called a Migration
Manager, that you can configure with this Mapping
| | 07:27 | Model and write a little bit of
code to convert one to the other.
| | 07:30 | But these more complex data migrations are
beyond the scope of this course, because they
| | 07:35 | are all different in their own way.
So I will refer you to the authoritative source.
| | 07:43 | Once you go beyond what Lightweight
Migration can do you're going to be interested in the
| | 07:47 | Core Data Model versioning and
Data Migration Programming guide.
| | 07:52 | This will be your guidebook if you need to
do more complex migrations involving mapping
| | 07:57 | models and adding custom code, if you need
to execute custom code during the process.
| | 08:01 | But you will find a Lightweight Migration
can actually take care of a lot of the issues
| | 08:07 | that you're going to run into.
| | Collapse this transcript |
|
|
ConclusionFinal thoughts: Back to the stack| 00:01 | What I wanted to do with this course is take
you through the things that you will always
| | 00:04 | need in every Core Data application you make,
managed objects, and managed object models,
| | 00:10 | managed object context, fetch requests and
fetch results controllers, validation, undo,
| | 00:15 | and redo, importing, and migration.
| | 00:17 | At the beginning of this course I showed a
diagram of interrelated objects, which should
| | 00:23 | hopefully make a lot more sense now,
because you have all the pieces in place.
| | 00:28 | We know that we start with the data model
defining our entities, the descriptions of
| | 00:33 | our attributes, our
default values and relationships.
| | 00:37 | Those entities will be gathered together into
a managed object model, that's what the data
| | 00:42 | model file will be turned into when our application
runs and those entities used to create our managed objects.
| | 00:50 | We have the all
important managed object context.
| | 00:54 | The beating heart of Core Data, the
scratchpad, or workbench, for all your managed objects.
| | 01:00 | It's the context that contains them all, it's
the context that we use to perform fetch requests
| | 01:04 | and saves to undo and redo.
| | 01:07 | And our context is connected to the
persistent store coordinator, and that doesn't need a
| | 01:12 | lot of input from us, but it's what will
connect us to the actual persistent store itself,
| | 01:17 | the store file, whatever format that might
take XML, SQLite, Binary, or even in memory.
| | 01:24 | Now of course, it can get deeper but this is
Core Data, this is the Core Data stack, it's
| | 01:29 | everything Core Data needs to work.
| | 01:32 | But as you continue experimenting with Core
Data there is a few things that you're likely
| | 01:36 | to encounter that I wanted to briefly talk
about. these are things not every app will
| | 01:40 | need, but occasionally yours might. You'll hear
about ideas like having multiple persistent stores.
| | 01:47 | There is nothing that limits you to just one
store file, that is the default, and that's
| | 01:52 | what most apps will need, but Core Data
supports using multiple separate store files. You could
| | 01:57 | have multiple stores that share the same data
model or stores with completely different data models.
| | 02:03 | Couple of reasons for this, one you might
have separate stores for different users or
| | 02:08 | separate stores that the application opens
up one at a time. Think of if you've used
| | 02:13 | iTunes libraries, large collections of data, but
you're allowed to close one down and then open another.
| | 02:19 | They share the same schema, but
they are different stores of data.
| | 02:24 | But even with multiple stores you don't have
to have the same data model, you could have
| | 02:27 | separate stores using different data models.
| | 02:30 | One reason for this might be one store that's
configured for user data, and you create one
| | 02:35 | model for all the user data, things that change
and are volatile whereas you have a completely
| | 02:40 | different store for say some predefined and
included object assets, and that would allow
| | 02:45 | you to manage them as two separate files.
| | 02:48 | Now neither of these you have to do,
nothing is required. All you should take away from
| | 02:53 | this is knowing that flexibility is
available should your app require.
| | 02:58 | Now the same idea is we can have
multiple managed object contexts.
| | 03:03 | All the default applications just provide one,
but if we're taking the idea of a managed
| | 03:08 | object context as being like an intelligent
workbench, or scratchpad, well you can provide more than one.
| | 03:15 | Say if you have very complex object creation
with a lot of dependencies, you might create
| | 03:19 | a separate managed object context, or
scratchpad just for that part of your app, which keeps
| | 03:26 | its own Undo Manager and can contain
multiple objects separate to the main context.
| | 03:31 | You might use it to create small object graphs
then save those from the secondary context
| | 03:37 | and load them into the main context.
| | 03:39 | But know that managed objects are always
created in a particular managed object context, so
| | 03:46 | there is no such thing as the same instance
in both, although you can fetch multiple copies
| | 03:51 | of the same object into different contexts,
but the contexts themselves are always saved
| | 03:56 | and handled independently.
| | 03:58 | Again, not required, but it is an option
that you can use, and hopefully you're getting
| | 04:02 | the idea that all these objects we've
being given are actually quite flexible.
| | 04:07 | And there is a term you might come
across called Faulting in Core Data.
| | 04:12 | The good thing about faulting
is you're already doing this.
| | 04:15 | Core Data is already doing faulting for you,
it's a way of efficiently handling memory.
| | 04:20 | A way of doing a lazy loading.
But this is what it formally refers to.
| | 04:25 | Let's say that I'm fetching a hundred
authors out of the store to show in a table view,
| | 04:30 | but in our data model all these authors have
too many relationships to multiple course objects.
| | 04:37 | So instead of a hundred objects that
entire object graph may be much, much bigger.
| | 04:42 | And here's the thing, I don't actually need
Core Data to retrieve all that data at once
| | 04:47 | if I'm only interested in the office.
| | 04:50 | Now Core Data is actually smart
enough to not fetch everything.
| | 04:54 | It understands that the author objects have
references to the course objects, but if I
| | 04:58 | haven't accessed them yet the course
objects won't have actually been fetched.
| | 05:03 | Core Data is waiting for me to need them.
| | 05:05 | This is faulting and
these are considered faults.
| | 05:09 | All these author objects will have a property that
refers to them, but that probably doesn't live yet.
| | 05:15 | As soon as we attempt to access that
property of the author object to get to that course
| | 05:19 | object, Core Data will immediately fetch
this first. This is called firing the fault, and
| | 05:25 | you don't have to do anything, you just
behave as if everything is there and available for
| | 05:30 | you all the time, it's completely
transparent to you as a developer.
| | 05:34 | However, in more complex
scenarios you can affect it.
| | 05:38 | There are things that you can do that affect faulting,
there's things like batch faulting and pre-fetching.
| | 05:43 | For example, if you knew that you should go
and grab all these course objects, you can
| | 05:48 | do some pre-fetching to make sure the
Core Data does go ahead and grabs them all.
| | 05:53 | However, be aware of faulting, but know
until you actually run into these problems don't
| | 05:59 | worry about faulting, it's there
already, it's already happening.
| | 06:03 | But for more on faulting, batch faulting,
pre-fetching, all of these advanced options,
| | 06:08 | and more, the document you should be keeping
handy is Apple's Core Data Programming Guide.
| | 06:14 | This is available from developer.apple.com, it's
about 200 pages of reference on all the Core Data options.
| | 06:21 | A lot of things we've seen
and a few advanced features.
| | 06:24 | So while there are other things we can do,
and there are advanced features of Core Data,
| | 06:28 | things like multithreading, you should
already have everything you need to be able to turn
| | 06:32 | that Core Data check box on or if you have
an existing project a pretty good idea of
| | 06:37 | what you need to copy across and
start to make it work with Core Data.
| | 06:41 | Thanks for joining me for this course and
let us know if there is anything you want to see.
| | 06:45 | See you next time!
| | Collapse this transcript |
|
|