Tym razem nieco informacji o Core Data, który jest takim uniwersalnym ORM mogącym współpracować z różnymi źródłami danych. Przy okazji dodam, że takie są założenia budowanego Entity Framework w wersji 7. Muszę przyznać, że API w Core Data wygląda całkiem przyjemnie, dodatkowo przewidziano współpracę np. z UITableView w zakresie grupowania (no, tu już API nie jest takie przyjemne, ale idea słuszna) czy stronicowania. Należy zauważyć możliwość szczegółowego profilowania operacji na danych. Brawo!
Intro
Core Data
- framework
- zarządzanie cyklem życia obiektów
- zarządzanie grafem obiektów
- persystencja
Stos
- NSManagedObject, NSManagedObjectContext
- NSPersistentStoreCoordinator (+ NSManagedObjectModel)
- NSPersistentStore (SQLite, XML, pamięć, iCloud, …)
Model
Elementy
- encje (odpowiednik tabel)
- atrybuty (odpowiednik kolumn; typ, walidacja: min, max, default, właściwości)
- relacje (to-one, to-many, opcje: m.in typ, opcjonalność, reguła usuwania)
- dziedziczenie
- designer (plik *.xcdatamodelId)
Zaawansowane opcje
- szablony zapytań
- wersjonowanie
- dodatkowe informacje w postaci słowników (user info dictionaries)
- konfiguracje
Prosty odczyt i zapis
XCode: projekt z opcją Core Data z wygenerowanym kodem
AppDelegate
@interface AppDelegate: UIResponder<UIApplicationDelegate>
…
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
- (void) saveContext;
- (NSURL *) applicationDocumentsDirectory;
@end
@implementation AppDelegate
- (NSManagedObjectModel *)managedObjectModel {
if (_managedObjectModel != nil) {
return _managedObjectModel;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataDemo" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];//inna możliwość: [NSManagedObjectModel mergedModelFromBundles: nil]
return _managedObjectModel;
}
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataDemo.sqlite"];
NSError *error = nil;
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
}
return _persistentStoreCoordinator;
}- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;
}- (void)saveContext {
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
NSError *error = nil;
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
}
}
}@end
Opis encji:
NSEntityDescription *desc;
desc = [NSEntityDescription entityForName: @”Album” inManagedObjectContext: context];
Wstawianie obiektu:
NSManagedObject *obj;
obj = [NSEntityDescription insertNewObjectForEntityForName: @”Album” inManagedObjectContext: context];
[obj setValue: @”Kazik” forKey: @”artist”];
[obj setValue: @”Melassa” forKey: @”title”];
NSString *title = [obj valueForKey: @”title”];
Pobieranie obiektów:
NSFetchRequest *req;
req = [NSFetchRequest fetchRequestWithEntityName: @”Album”];
NSError *error;
NSArray *allAlbums = [context executeFetchRequest: req error: &error];
Odczytywanie i zapisywanie - bardziej złożone scenariusze
Własne klasy encji:
dziedziczymy po NSManagedObject
@interface Album: NSManagedObject
@property (nonatomic, retain) NSString* artist;
@property (nonatomic, retain) NSString* title;
@end
@interface Album (CoreDataGeneratedAccessors)
…
@end
@implementation Album
@dynamic artist; //implementacja dostarczana w runtime
@dynamic title;
- (void) awakeFromInsert {
[self setTitle: @”No title”]; //w przypadku niedostarczenia danych
}
@end
nie należy nadpisywać:
- primitiveValueForKey:
- setPrimitiveValue: forKey:
- isEqual:
- hash
- superclass
- class
- self
- zone
- isProxy
- managedObjectContext
- entity
- description
implementujemy:
- awakeFromInsert
- awakeFromFetch
kreator w XCode: nowy plik –> Core Data –> NSManagedObject subclass
używanie wygenerowanych klas:
Album *album;
album = [NSEntityDescription insertNewObjectForEntityForName: @”Album” inManagedObjectContext: context];
[album setTitle: @”Hurra”];
NSString *title = [album title];
Relacje:
NSManagedObject *category = [item valueForKey: @”category”];
MusicCategory *category = [item category]; //przy własnych klasach encji
NSMutableSet *songs = [item mutableSetValueForKey: @”songs”];
[songs addObject: song];
[item addSongsObject: song]; //wygenerowana metoda we własnej klasie encji
Zapytania:
Zwracają obiekty NSManagedObject albo nasze encje. Encje powiązane są ładowane.
NSPredicate:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: @”Album”];
NSPredicate *pred = [NSPredicate predicateWithFormat: @”title CONTAINS %@”, searchString];
NSPredicate *pred = [NSPredicate predicateWithFormat: @”%K CONTAINS %@”, attr, searchString];
[request setPredicate: pred];
porównanie napisu:
- CONTAINS
- BEGINSWITH
- ENDSWITH
- LIKE
- MATCHES
proste porównanie:
- =, ==
- >=, =>
- <=, =<
- <
- >
- !=, <>
- BETWEEN
agregacje:
- ANY, SOME
- ALL
- NONE
- IN
NSSortDescriptor:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: @”Album”];
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey: @”title” ascending: YES];
NSArray *sortArray = [NSArray arrayWithObject: sort];
[request setSortDescriptors: sortArray];
Szablony zapytań
możliwość definiowania i trzymania w diagramie
NSFetchRequest *request = [model fetchRequestTemplateForName: @”allRockAlbums”];
NSFetchedResultsController
Prezentowanie danych w sekcjach UITableView:
sekcje: pierwszy deskryptor w kolekcji z obiektami NSSortDescriptor w NSFetchRequest
sortowanie w sekcjach: drugi deskryptor
@interface ViewController: UITableViewController <NSFetchedResultsControllerDelegate>
@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
@end
@implementation ViewController
…
- (void) viewDidLoad {
…
NSError *error;
if (![[self fetchedResultsController] performFetch: &error]) {
}
}
- (NSFetchedResultsController *) fetchedResultsController {
…
//przygotowanie requestu z deskryptorami do sortowania, pierwszy z “category.name”
NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest: request
managedObjectContext: context sectionNameKeyPath: @”category.name” cacheName: nil ];
[self setFetchedResultsController: frc];
[[self fetchedResultsController] setDelegate: self];
return _fetchedResultsController;
}
- (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView {
NSArray *sections = [[self fetchedResultsController] sections];
return [sections count];
}
- (NSInteger) tableView: (UITableView *) tableView
numberOfRowsInSection: (NSInteger) section {
NSArray *sections = [[self fetchedResultsController] sections];
id<NSFetchedResultsSectionInfo> curSec;
curSec = [sections objectAtIndex: section];
return [curSec numberOfObjects];
}
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath {
…
Album *item = [[self fetchedResultsController] objectAtIndexPath: indexPath];
…
}
- (NSString *) tableView: (UITableView *) tableView titleForHeaderInSection: (NSInteger) section {
NSArray *sections = [[self fetchedResultsController] sections];
id<NSFetchedResultsSectionInfo> curSec = [sections objectAtIndex: section];
return [curSec name];
}
//nasłuchiwanie zmian danych
- (void) controllerWillChangeContent: (NSFetchedResultsController *) controller {
[[self tableView] beginUpdates];
}
- (void) controller: (NSFetchedResultsController *) controller
didChangeSection: (id<NSFetchedResultsSectionInfo>) sectionInfo
atIndex: (NSUInteger) sectionIndex
forChangeType: (NSFetchedResultsChangeType) type {
switch (type) {
case NSFetchedResultsChangeInsert:
[[self tableView] insertSections: [NSIndexSet indexSetWithIndex: sectionIndex]
withRowAnimation: UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[[self tableView] deleteSections: [NSIndexSet indexSetWithIndex: sectionIndex]
withRowAnimation: UITableViewRowAnimationFade];
break;
}
}
- (void) controller: (NSFetchedResultsController *) controller
didChangeObject: (id) anObject
atIndexPath: (NSIndexPath *) indexPath
forChangeType: (NSFetchedResultsChangeType) type
newIndexPath: (NSIndexPath *) newIndexPath {
switch (type) {
case NSFetchedResultsChangeInsert:
[[self tableView] insertRowsAtIndexPaths: [NSArray arrayWithObject: newIndexPath]
withRowAnimation: UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[[self tableView] deleteRowsAtIndexPaths: [NSArray arrayWithObject: indexPath]
withRowAnimation: UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
{
UITableViewCell *cell = [[self tableView] cellForRowAtIndexPath: indexPath];
Album *item = [[self fetchedResultsController] objectAtIndexPath: indexPath];
[[cell textLabel] setText: [item title];
break;
}
case NSFetchedResultsChangeMove:
[[self tableView] deleteRowsAtIndexPaths: [NSArray arrayWithObject: indexPath]
withRowAnimation: UITableViewRowAnimationFade];
[[self tableView] insertRowsAtIndexPaths: [NSArray arrayWithObject: newIndexPath]
withRowAnimation: UITableViewRowAnimationFade];
break;
}
}
- (NSString *) controller: (NSFetchedResultsController *) controller
sectionIndexTitleForSectionName: (NSString *) sectionName {
return sectionName;
}
- (void) controllerDidChangeContent: (NSFetchedResultsController *) controller {
[[self tableView] endUpdates];
}
@end
Wersjonowanie modelu
po zmianie modelu trzeba zmigrować dane w repozytorium
migracje
- dodawanie, usuwanie, zmiana nazw atrybutów, encji, relacji
- tworzenie encji rodziców i dzieci
- przesuwanie atrybutów w górę i w dół hierarchii encji
konfiguracja automatycznych migracji:
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setValue: [NSNumber numberWithBool: YES]
forKey: NSMigratePersistentStoresAutomaticallyOption];
[options setValue: [NSNumber numberWithBool: YES]
forKey: NSInferMappingModelAutomaticallyOption];
NSError *error = nil;
[persistentStoreCoordinator addPersistentStoreWithType: NSSQLiteStoreType
configuration: nil
URL: dataStoreURL
options: options
error: &error];
tworzymy nową wersję modelu: Editor -> Add Model Version…
ustawiamy nowy model jako bieżący: wybieramy w ustawieniach katalogu z modelami
wykonujemy zmiany na nowej wersji, w ustawieniach elementu uzupełniamy w sekcji Versioning np. poprzednią nazwę
poprzednio wygenerowane klasy encji albo ręcznie edytujemy albo generujemy jeszcze raz
bardziej zaawansowane scenariusze: tworzenie modeli mapujących pomiędzy wskazanymi wersjami modelu
Wydajność
Model
- denormalizacja (zamiast relacji atrybut z obiektem tego rodzaju)
- dodanie indeksu na atrybucie (opcja Indexed)
- tworzenie indeksu na wielu kolumnach (w encji sekcja Indexes)
- przechowywanie w postaci binarnej atrybutu (np. zdjęcia, typ: Binary Data, opcja: Allows External Storage)
Refaulting:
[context refreshObject: item mergesChanges: NO];
[context reset];
Prefetching:
NSEntityDescription *entity = [NSEntityDescription entityForName: @”Album” inManagedObjectContext: context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity: entity];
NSMutableArray *prefetchKeys = [NSMutableArray array];
[prefetchKeys addObject: @”category”];
[request setRelationshipKeyPathsForPrefetching: prefetchKeys]; //ładowanie od razu encji we wskazanych miejscach
Ograniczenie pobieranych danych:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: @”Album”];
[request setFetchBatchSize: 10]; //doładowywanie na żądanie, jeśli będzie używane, współpraca z UITableView
Wydajne tekstowe zapytania:
np. < przed CONTAINS w koniunkcji
Rodzaje współbieżności:
- NSConfinementConcurrencyType
- NSMainQueueConcurrencyType
- NSPrivateQueueConcurrencyType
NSUInteger type = NSPrivateQueueConcurrencyType;
_context = [[NSManagedObjectContext alloc] initWithConcurrencyType: type];
[_context performBlock: ^{
}];
Debugowanie:
w ustawieniach projektu w sekcji Run na zakładce Arguments dodajemy argument: -com.apple.CoreData.SQLDebug 1
Profilowanie:
XCode: Product –> Profile –> Core Data
Uzupełnienia
UIPickerView - odpowiednik comba
@interface ViewController: UIViewController <UIPickerViewDelegate, UIPickerViewDataSource>
@property (weak, nonatomic) IBOutlet UIPickerView *picker;
@property (nonatomic, strong) NSArray *allCategories;
@property (nonatomic, strong) MusicCategory *selectedCategory;
@end
@implementation ViewController
- (void) viewDidLoad {
…
[[self picker] setDelegate: self];
[[self picker] setDataSource: self];
[[self picker] selectRow: 0 inComponent: 0 animated: NO];
[self setSelectedCategory: [[self allCategories] objectAtIndex: 0]];
}
…
//UIPickerViewDataSource
- (NSInteger) numberOfComponentsInPickerView: (UIPickerView *) pickerView {
return 1;
}
- (NSInteger) pickerView: (UIPickerView *) pickerView numberOfRowsInComponent: (NSInteger) component {
return [[self allCategories] count];
}
//UIPickerViewDelegate
- (NSString *) pickerView: (UIPickerView *) pickerView
titleForRow: (NSInteger) row
forComponent: (NSInteger) component {
MusicCategory *category = [[self allCategories] objectAtIndex: row];
return [category categoryName];
}
- (void) pickerView: (UIPickerView *) pickerView
didSelectRow: (NSInteger) row
inComponent: (NSInteger) component {
MusicCategory *selected = [[self allCategories] objectAtIndex: row];
[self setSelectedCategory: selected];
}
@end
Programowe wyłączenie klawiatury na polu tekstowym
[textField resignFirstResponder];
Programowa zmiana zakładki w UITabBarController z poziomu jednej z zakładek
[[self tabBarController] setSelectedIndex: 0];
Brak komentarzy:
Prześlij komentarz