6. Diego Freniche / @dfreniche / http://www.freniche.com
Core Data Hello World!
7. Diego Freniche / @dfreniche / http://www.freniche.com
That’s easy peasy
8. Diego Freniche / http://www.freniche.comDiego Freniche / @dfreniche / http://www.freniche.com
Apple’s code
!
- (void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
}
!#pragma mark - Core Data stack
!// Returns the managed object context for the application.
// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
!// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
- (NSManagedObjectModel *)managedObjectModel
{
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Test" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
!// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Test.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
@{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES}
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
!#pragma mark - Application's Documents directory
!// Returns the URL to the application's Documents directory.
- (NSURL *)applicationDocumentsDirectory
{
return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}
9. Diego Freniche / @dfreniche / http://www.freniche.com
Corollary
• Never, ever use the Core Data checkbox
• BIG design flaws:
• Core Data Stack coupled to AppDelegate:
• can’t reuse in other projects
• separations of concerns, anyone?
• ugly! and just ONE Core Data Stack
• They don’t even use a custom class
10. Diego Freniche / @dfreniche / http://www.freniche.com
What is Core Data?
• Poll
• A Framework. Must be important: its name starts with “Core”
• An ORM
• The model of your App
• Technology to help you that has a steep learning curve
11. Diego Freniche / @dfreniche / http://www.freniche.com
One of the problems
• Objet - Relational impedance
• SQL: 70s/80s
• OOP: 80s-
12. Diego Freniche / @dfreniche / http://www.freniche.com
Exhibit 1: composition
id name idCard
Person
+name: String
+idCard: idCard
+bankAccout: bankAccount
idCard
+number: int
+letter: char
+checkLetter()
VARCHAR ¿?
13. Diego Freniche / @dfreniche / http://www.freniche.com
Employee
+empNumber: int
Person
+name: String
+idCard: idCard
Exhibit 2: inheritance
Hyena
Politician
+moneyTaken: double
14. Diego Freniche / @dfreniche / http://www.freniche.com
Exhibit 3: collection modelling
Person
+name: String
+bills: Bill[]
Bill
0..*1
http://en.wikipedia.org/wiki/First_normal_form
15. Diego Freniche / @dfreniche / http://www.freniche.com
Relational World
• tables
• cartesian products
• rows/ columns
• normal forms
• Objects
• object collections
• composition
• inheritance
OO World
16. Diego Freniche / @dfreniche / http://www.freniche.com
The solution: ORM
• Object Relational Mapper
• Hibernate (Java, .Net)
• Core Data (Cocoa)
• Core Data is our model
• can persist our objects
in several ways
17. Diego Freniche / @dfreniche / http://www.freniche.com
Which problems will Core Data help me solve?
• Persist my data
• Cache online data
• to speed up things
• to show something to the user when (if) there’s no Internet connection
• Navigate the object graph:
• [([Company allEmployees][0]).boss name];
• Maintain ViewControllers in sync
18. Diego Freniche / @dfreniche / http://www.freniche.com
Don’t fight the Frameworks!
• Yes! You need to learn them!
• Everything well thought-out to work with Core Data
• Working with databases without Core Data?
• not so much fun!
• reinvent the wheel
• this is not JavaScript-Land!
19. Diego Freniche / @dfreniche / http://www.freniche.com
Core Data terms
• Entity
• an entity in our model == Object in memory == row in table
• Attribute
• Relationship
• Object graph
http://ogre.berniecode.com/ogre_white_paper
20. Diego Freniche / @dfreniche / http://www.freniche.com
To get the most out of Core Data
• You need to understand:
• KVC, KVO
• ARC, memory management
• delegate & MVC patterns
• that singletons are evil (more or less)
21. Diego Freniche / @dfreniche / http://www.freniche.com
The Core Data Stack
Managed Object Context
Persistent Store Coordinator
Persistent Object Store
Managed Object Model
22. Diego Freniche / @dfreniche / http://www.freniche.com
Managed Object Context
Managed Object Context
Persistent Store Coordinator
Persistent Object Store
Managed Object Model
23. Diego Freniche / @dfreniche / http://www.freniche.com
Our Model
NSManagedObject
NSManagedObject
NSManagedObject
NSManagedObject
Persisted
NSManagedObjectContext
NSObject
In memory only
25. Diego Freniche / @dfreniche / http://www.freniche.com
Managed Object Context: MOC
• Managed Object Context: in-memory space where CD manages all our
model’s objects.
• All CRUD is done against a MOC. We persist data using [context save:]
• Our model’s objects are Managed Objects.
• The MOC needs a Persistent Store Coordinator to save the object graph in
persistent store.
26. Diego Freniche / @dfreniche / http://www.freniche.com
Managed Object Model
Managed Object Context
Persistent Store Coordinator
Persistent Object Store
Managed Object Model
27. Diego Freniche / @dfreniche / http://www.freniche.com
Managed Object Model
• Maps our model objects into database tables.
• Objects == NSManagedObject
• Classes == NSEntityDescription
• We describe our App entities inside a MOM
• stored inside .xcdatamodeld files in Xcode. Compiles into .momd
• graphic editor / class generator (dumb)
28. Diego Freniche / @dfreniche / http://www.freniche.com
Managed Object Model
Managed Object Context
Persistent Store Coordinator
Persistent Object Store
Managed Object Model
29. Diego Freniche / @dfreniche / http://www.freniche.com
Persistent Store Coordinador
Managed Object Context
Persistent Store Coordinator
Persistent Object Store
Managed Object Model
Makes the mapping between our App’s
objects and the physical storage inside
the Persistent Object Store.
99% time we’ll work with ONE Object
Store, but it’s possible use more than
one. For example, a sqlite DB with
recipes and another DB with notes,
stars, etc. Coordinator: single façade
to work with different Stores.
!
A managed object context can then
create an object graph based on the
union of all the data stores the
coordinator covers
Persistent Object Store
30. Diego Freniche / http://www.freniche.com
Persistent Store Coordinador
Called Coordinator for a reason:
serializes operations
!
Core Data API is NOT Thread safe
!
Persistent Store Coordinator makes
it Thread Safe
Managed Object Context
Persistent Store Coordinator
Persistent Object Store
Managed Object Model
Managed Object Context
Managed Object Context
31. Diego Freniche / @dfreniche / http://www.freniche.com
Persistent Object Store
Managed Object Context
Persistent Store Coordinator
Persistent Object Store
Managed Object Model
NSXMLStoreType (XML only OS X, bad performance)!
NSSQLiteStoreType (partial object graph in memory)!
NSBinaryStoreType (kind of NIBs, poor performance)!
NSInMemoryStoreType (good for testing)
Makes the mapping between our App’s objects and the physical storage inside
the Persistent Object Store.
Supported Store Types
33. Diego Freniche / http://www.freniche.com
DF Core Data Stack
Warning: really bad code ahead!
34. Diego Freniche / http://www.freniche.comDiego Freniche / @dfreniche / http://www.freniche.com
Dependency injection? Or singletons FTW?
• It depends :-)
35. Diego Freniche / http://www.freniche.com
To create a Core Data Project
• Link against Core Data Framework
• Write some code to create a Core Data Stack
• Test it! In memory (better)
36. Diego Freniche / @dfreniche / http://www.freniche.com
SQL in SQLite
• http://sqlite.org/lang.html
• We can have a look at generated SQL:
• -com.apple.CoreData.SQLDebug 1
• Schemes > Edit Scheme > Test > Arguments
38. Diego Freniche / @dfreniche / http://www.freniche.com
Accessing SQLite
sqlitestudio.pl
Look for your databases in:
!
/Users/<usuario>/Library/Application
Support/iPhone Simulator/<versión>/
Applications/<ID App>/Documents/
<archivo SQLite>
39. Diego Freniche / @dfreniche / http://www.freniche.com
DDL: Data Definition Language
• CREATE DATABASE, CREATE TABLE, CREATE INDEX, ALTER TABLE, DROP
INDEX, ...
• All written by Core Data
• We DO NOT have to create anything: neither tables nor database
• If we make changes, Core Data alters tables, columns, indexes...
40. Diego Freniche / @dfreniche / http://www.freniche.com
Modelling
We need a Data Model file to “draw” our model
1st, create all our model’s entities, then add
attributes!
!
Can subclass NSManagedObject to use compiler-
time name checking, Xcode’s autofill,...
41. Diego Freniche / @dfreniche / http://www.freniche.com
Modelling
• New model versions: Editor > Add
Model Version
• Model (.xcdatamodeld) is a folder
• Last version has no number. Oldest
with higher number. WTF, Apple!
(WTF: What a Tricky Fact)
• 1st time we access model it
creates persintent store lazily
Select THIS
42. Diego Freniche / @dfreniche / http://www.freniche.com
Versioning and changes
• Activate always lightweight migration
• If we make changes to the model, not changing version, version used in the
model and version used to create DB doesn’t match: delete DB.
• Reseting Content and Settings.. in Simulator
• delete (by hand) .sqlite file
44. Diego Freniche / @dfreniche / http://www.freniche.com
NSManagedObject
• base class implementing all “basic” object model behavior
• We can NOT use Core Data with NSObject, we HAVE TO use
NSManagedObject
• Our model classes inherit from NSManagedObject
• not mandatory, but...
• allows us to add logic, use @property, get notifications...
• have to be properly configured
45. Diego Freniche / @dfreniche / http://www.freniche.com
Entities Design
• Always add an order field
• Try to create a good UML diagram at first
• Yes, I’ve said UML!
• Have an NSString constant with every Entity’s name inside .h
46. Diego Freniche / @dfreniche / http://www.freniche.com
Extend NSManagedObject
• Editor > Create NSManagedObject subclass...
• creates @dynamic properties
• getter / setter generated in runtime (@property in compile time)
• Core Data doesn’t know at compile time if the persistent store is going to
be XML or a DB (or in-memory)
47. Diego Freniche / http://www.freniche.comDiego Freniche / @dfreniche / http://www.freniche.com
Entities Design Tips
• Always add field order
• Try to create a good UML diagram at first
• Have an NSString constant with every Entity’s name inside .h
48. Diego Freniche / http://www.freniche.comDiego Freniche / @dfreniche / http://www.freniche.com
Extend NSManagedObject
• Editor > Create NSManagedObject subclass...
• creates @dynamic properties
• getter / setter generated in runtime (@property in compile time)
• Core Data doesn’t know at compile time if the persistent store is going to
be XML or a DB (or in-memory)
50. Diego Freniche / @dfreniche / http://www.freniche.com
Validate Properties
• One for every property, if we want it
• Passing parameter by reference
• It should return YES if validation is passed
!
-(BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError;
51. Diego Freniche / @dfreniche / http://www.freniche.com
Validator for operations
• First thing: must call [super ...]
• Useful to check business rules (using several properties)
!
- (BOOL)validateForDelete:(NSError **)error
- (BOOL)validateForInsert:(NSError **)error
- (BOOL)validateForUpdate:(NSError **)error
52. Diego Freniche / @dfreniche / http://www.freniche.com
Support for KVO
• Good for Faults
!
- (void)willAccessValueForKey:(NSString *)key
53. Diego Freniche / @dfreniche / http://www.freniche.com
Inserting Entities (INSERT INTO)
!
// using NSEntityDescription. Pass: entity name and context
[NSEntityDescription insertNewObjectForEntityForName:@”RetroItems” inManagedObjectContext:context];
[retroItem setValue:@"Spectrum 48K" forKey:@"name"];
[retroItem setValue:@100.00 forKey:@"acquisitionCost"];
[retroItem setValue:[NSDate new] forKey:@"dateAcquired"];
insertNewObjectForEntityForName context save
Context
54. Diego Freniche / @dfreniche / http://www.freniche.com
Fetching entities (SELECT)
• Query with NSFetchRequest
• SELECT by definition is unordered
• At least we need to provide
• entity
• order by
56. Diego Freniche / @dfreniche / http://www.freniche.com
Order querys
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"fecha_max" ascending:YES];
NSManagedObject returned inside NSArray are unordered unless otherwise we
provide NSSortDescription.
!
1st, we create the sort descriptor
[request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
Then we add to our NSFetchRequest the sort descriptors array:
57. Diego Freniche / @dfreniche / http://www.freniche.com
Filtering querys
NSManagedObjectContext *moc = [self managedObjectContext];!
!
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Employee"
inManagedObjectContext:moc];!
!
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];!
[request setEntity:entityDescription];!
!
NSNumber *minimumSalary = ...;!
!
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(lastName LIKE[c] 'Worsley') AND (salary
> %@)", minimumSalary];!
!
[request setPredicate:predicate];!
!
NSError *error = nil;!
!
NSArray *array = [moc executeFetchRequest:request error:&error];!
NSFetchRequest returns an array of NSManagedObject. We can use a
NSPredicate to filter.
!
Filter/NSPredicate acts here as the SQL WHERE clause.
!
We need to add that query with setPredicate.
58. Diego Freniche / @dfreniche / http://www.freniche.com
• CONTAINS: to query for strings that contain substrings.
• ==: equality operator.
• BEGINSWITH: a pre-made regular expression that looks for matches at the
beginning of a string.
• MATCHES: regular expression-like search.
• ENDSWITH: opposite of BEGINSWITH.
• <, >: less than and greater than.
61. Diego Freniche / @dfreniche / http://www.freniche.com
Accessing Properties
Sometimes we don’t want the whole object (NSManagedObject) only the value of a property applying a function
(max, min, etc.).
NSFetchRequest *request = [[NSFetchRequest alloc] init];!
!
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event" inManagedObjectContext:context];!
!
[request setEntity:entity];
1. Create NSFetchRequest as usual
[request setResultType:NSDictionaryResultType];
2. Tell NSFetchRequest to return NSDictionary instead NSArray:
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:@"creationDate"];!
!
NSExpression *minExpression = [NSExpression expressionForFunction:@"min:" arguments:[NSArray
arrayWithObject:keyPathExpression]];
3. Define the field to calc upon (NSExpression), then the function
62. Diego Freniche / @dfreniche / http://www.freniche.com
Accessing Properties
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];!
!
[expressionDescription setName:@"minDate"];!
!
[expressionDescription setExpression:minExpression];!
!
[expressionDescription setExpressionResultType:NSDateAttributeType];
4. Create a NSExpressionDescription:
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];!
!
NSArray *objects = [managedObjectContext executeFetchRequest:request error:&error];
5. Set in the query the properties to fetch
if ([objects count] > 0) {!
NSLog(@"Minimum date: %@", [[objects objectAtIndex:0] valueForKey:@"minDate"]);!
}
6. Access our result:
63. Diego Freniche / @dfreniche / http://www.freniche.com
Using Blocks to Sort
If Sorting is not trivial maybe NSSortDescription is not enough.
[[NSSortDescriptor alloc] initWithKey:<#(NSString *)#>
ascending:<#(BOOL)#>
comparator:<#^NSComparisonResult(id obj1, id obj2)cmptr#>
^(id a, id b) {
NSNumber *lat1 = [[a valueForKey:@"latitude"] doubleValue];
NSNumber *lon1 = [a valueForKey:@"longitude"] doubleValue];
CLLocation *loc1 = [[CLLocation alloc] initWithLatitude:lat1 longitude:lon1];
NSNumber *lat2 = [[b valueForKey:@"latitude"] doubleValue];
NSNumber *lon2 = [b valueForKey:@"longitude"] doubleValue];
CLLocation *loc2 = [[CLLocation alloc] initWithLatitude:lat2 longitude:lon2];
CLLocationDistance dist_a= [loc1 distanceFromLocation:locUser];
CLLocationDistance dist_b= [loc2 distanceFromLocation:locUser];
if ( dist_a < dist_b ) {
return (NSComparisonResult)NSOrderedAscending;
} else if ( dist_a > dist_b) {
return (NSComparisonResult)NSOrderedDescending;
} else {
return (NSComparisonResult)NSOrderedSame;
}
}];
Ejemplo de ordenación de distancias:
66. Diego Freniche / @dfreniche / http://www.freniche.com
NSFetchedResultsController
• Controller without interface
• Purpose: “feed” with data an UITableView
• Protocol NSFetchedResultsControllerDelegate
• section “Typical Use”: there’s the code found in template
• connected to a Context: if there are changes of any object inside that
context it receives a notification and updates automatically
70. Diego Freniche / http://www.freniche.comDiego Freniche / @dfreniche / http://www.freniche.com
Mogenerator
created by Jonathan 'Wolf' Rentzsch
71. Diego Freniche / http://www.freniche.comDiego Freniche / @dfreniche / http://www.freniche.com
Mogenerator (quoting from the web page)
• http://rentzsch.github.io/mogenerator/
• generates Objective-C code for your Core Data custom classes
• Unlike Xcode, mogenerator manages two classes per entity: one for
machines, one for humans
• The machine class can always be overwritten to match the data model,
with humans’ work effortlessly preserved
73. Diego Freniche / http://www.freniche.comDiego Freniche / @dfreniche / http://www.freniche.com
Two classes
• _MyClass.*: machine generated
• *MyClass.*: human edited
!
• Never, ever recreate the classes
again from the Core Data Model
75. Diego Freniche / http://www.freniche.comDiego Freniche / @dfreniche / http://www.freniche.com
Using it
• it’s a script, so we can launch it from command line
• using iTerm, DTerm, etc.
• Best way: to have it inside our project
• Create a new Aggregate Target (New Target > Other > Aggregate)
• Add Build Phase > Add Run Script
!
mogenerator --template-var arc=true -m RetroStuffTracker/
RetroStuffTracker.xcdatamodeld/RetroStuffTracker.xcdatamodel/
77. Diego Freniche / http://www.freniche.com
Idea: use Magical Record
78. Diego Freniche / http://www.freniche.com
Magical Record Demo
Warning: really bad code ahead!
79. Diego Freniche / http://www.freniche.com
Magical record != avoid Core Data at all costs
• Just a bunch of categories to help you write less code
• You have to know your sh*t
• CocoaPods friendly
• Ideal: use Unit testing + Mogenerator + CocoaPods + Magical Record
• My point: 7 people, 7 ideas, all great
• all different
80. Diego Freniche / http://www.freniche.com
Idea: using asserts to check threads
81. Diego Freniche / http://www.freniche.com
Asserts
• Check if we are running UI code in the UI Thread
• Check if we are NOT running Core Data code in the UI Thread
!
#ifndef
CDHelloWord_DFThreadAsserts_h
#define
CDHelloWord_DFThreadAsserts_h
!
#define
DF_ASSERT_MAIN_THREAD
[NSThread
isMainThread]?:(NSLog(@"NOT
IN
MAIN
THREAD"),abort())
#define
DF_ASSERT_NOT_MAIN_THREAD
![NSThread
isMainThread]?:(NSLog(@"IN
MAIN
THREAD,
BUT
NOT
EXPECTED"),abort())
!
!
#endif
82. Diego Freniche / http://www.freniche.com
Idea: create a common UITableView/Core data
class