воскресенье, 12 июня 2016 г.

41. CoreData Part 1 Basics

Первый урок по CoreData.

Создаем пустой проект и указываем галочку Use CoreData.

Для отслеживания (дебага) запросов к CoreData выбираем схему (левый верхний угол окна) -> edit Scheme -> Слева выбираем Run и переходим на вкладку Arguments.
Добавляем новый параметр (+ нажать) в блок Arguments Passed On Launch:
-com.apple.CoreData.SQLDebug 1

В проекте присутствует 3 новых объекта

Модель  NSManagedObjectModel *managedObjectModel  связана с файлом в котором хранятся описания всех сущностей и связей, которые хранятся в базе данных.

Координатор NSPersistentStoreCoordinator *persistentStoreCoordinator он осуществляет считывание и сохранение данных в файл базы данных. Он использует модель, чтобы знать какие сущности хранятся в файле базы данных.

Контекст  NSManagedObjectContext *managedObjectContext  это та среда которая используется в приложении для создания, сохранения объектов в базу данных. Приложение взаимодействует с базой данных через контекст.  Контекст связан с координатором,  который отвечает за непосредственный обмен данных с файлом базы данных.     

Для создания новой сущности/модели нужно зайти в файл модели с расширением .xcdatamodeld и нажать кнопку Add Entity
Назовем новую сущность, так же как класс TAStudent
Добавим атрибуты в сущность fistName, LastName  - типа String, dateBirth - типа Date, score -типа Double.

Для того, чтобы создать объект в кордате он должен быть типа NSManagedObject. 
Добавляем объект студент - сущьность типа TAStudent и инициализируем его с помощью KVC:


 NSManagedObject *student = [NSEntityDescription insertNewObjectForEntityForName:@"TAStudent" inManagedObjectContext:self.managedObjectContext]; 
 [student setValue:@"Vasya" forKey:@"firstName"];
    [student setValue:@"Pupkin" forKey:@"lastName"];
    [student setValue:@3.8 forKey:@"score"];
    [student setValue:[NSDate dateWithTimeIntervalSinceReferenceDate:-60*60*24*365*10] forKey:@"dateBirth"];
    NSError *error;
    if (![self.managedObjectContext save:&error]) {
        NSLog(@"Error: %@", [error localizedDescription]);

    }

Если приложение падает с ошибкой, то это происходит из-за того, что первоначально в базе не было сущности, а мы добавили ее позже, а файл уже создан. Из-за этого происходит конфликт.
Самый простой способ это удалить приложение Simulator -> Hardware -> Home.

Можно добавить строчку, чтобы при конфликте старый файл удалялся. В функцию - (NSPersistentStoreCoordinator *)persistentStoreCoordinator {  

После условия 

if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {

Добавляем 

[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];

Для того, что сделать запрос из базы данных:

    NSFetchRequest *request = [[NSFetchRequest alloc]init];
    
    NSEntityDescription *description  = [NSEntityDescription entityForName:@"TAStudent" inManagedObjectContext:_managedObjectContext];
    [request setEntity:description];
    NSError *requestError = nil;
    NSArray *arrayResult = [self.managedObjectContext executeFetchRequest:request error:&requestError];
    if (requestError) {
        NSLog(@"Request Error: %@", [requestError localizedDescription]);
    }
    else{
        NSLog(@"Result %@", arrayResult);

    }

Можно выбрать тип результата для запроса, например результат в виде дикшионари для удобного отображения:

[request setResultType:NSDictionaryResultType];

Можно выдать результат в виде количества - объектов NSCountResultType, если сами объекты не нужны.

Если результат в виде массива объектов NSManagedObject, то вывести их можно так

    for (NSManagedObject *object in arrayResult) {
        NSLog(@"%@ %@ = %@", [object valueForKey:@"firstName"], [object valueForKey:@"lastName"], [object valueForKey:@"score"]);
    }

Для удобства работы с сущностями мы можем создать специальный класс наследник NSManagedObject, для этого добавляем в проект файл, слева выбираем Core Data, справа NSManagedObject subclass, выбираем файл с моделью и нашу сущность на основе нее будет создан новый класс.
Теперь для вывода результата можно использовать этот класс:

    for (TAStudent *student in arrayResult) {
        NSLog(@"%@ %@ = %@", student.firstName, student.lastName, student.score);

    }

Добавим функцию для создания рандомного студента.
Из прошлых проектов можно взять массивы имен и фамилий студентов.

static NSString* firstNames[] = {
    @"Tran", @"Lenore", @"Bud", @"Fredda", @"Katrice",
    @"Clyde", @"Hildegard", @"Vernell", @"Nellie", @"Rupert",
    @"Billie", @"Tamica", @"Crystle", @"Kandi", @"Caridad",
    @"Vanetta", @"Taylor", @"Pinkie", @"Ben", @"Rosanna",
    @"Eufemia", @"Britteny", @"Ramon", @"Jacque", @"Telma",
    @"Colton", @"Monte", @"Pam", @"Tracy", @"Tresa",
    @"Willard", @"Mireille", @"Roma", @"Elise", @"Trang",
    @"Ty", @"Pierre", @"Floyd", @"Savanna", @"Arvilla",
    @"Whitney", @"Denver", @"Norbert", @"Meghan", @"Tandra",
    @"Jenise", @"Brent", @"Elenor", @"Sha", @"Jessie"
};

static NSString* lastNames[] = {
    
    @"Farrah", @"Laviolette", @"Heal", @"Sechrest", @"Roots",
    @"Homan", @"Starns", @"Oldham", @"Yocum", @"Mancia",
    @"Prill", @"Lush", @"Piedra", @"Castenada", @"Warnock",
    @"Vanderlinden", @"Simms", @"Gilroy", @"Brann", @"Bodden",
    @"Lenz", @"Gildersleeve", @"Wimbish", @"Bello", @"Beachy",
    @"Jurado", @"William", @"Beaupre", @"Dyal", @"Doiron",
    @"Plourde", @"Bator", @"Krause", @"Odriscoll", @"Corby",
    @"Waltman", @"Michaud", @"Kobayashi", @"Sherrick", @"Woolfolk",
    @"Holladay", @"Hornback", @"Moler", @"Bowles", @"Libbey",
    @"Spano", @"Folson", @"Arguelles", @"Burke", @"Rook"

};

- (TAStudent *)addRandomStudent{
    TAStudent *student = [NSEntityDescription insertNewObjectForEntityForName:@"TAStudent" inManagedObjectContext:_managedObjectContext];
    student.score = @(arc4random_uniform(201)/100.f +2.f);
    student.dateBirth = [NSDate dateWithTimeIntervalSince1970:arc4random_uniform(20)*60*60*24*365];
    student.lastName = lastNames[arc4random_uniform(50)];
    student.firstName = firstNames[arc4random_uniform(50)];
    return student;

}

Выполняем эту функцию и сохраняем контекст:

    [self addRandomStudent];
    [self.managedObjectContext save:nil];

Каждый объект имеет ссылку на контекст, поэтому если мы находимся в другом классе (где нет self.managedObjectContext можно вызвать сохранение контекста так:

    TAStudent *student = [self addRandomStudent];
    [student.managedObjectContext save:nil];

Для переопределения сеттера и геттера в классе наследнике NSManagedObject, необходимо сообщить обсерверам CoreData об изменении проперти для сеттера или про получения доступа для геттера:

- (void)setFirstName:(NSString *)firstName
{
    [self willChangeValueForKey:@"firstName"];
    [self setPrimitiveValue:firstName forKey:@"firstName"];
    [self didChangeValueForKey:@"firstName"];
    NSLog(@"SET FIRST NAME");
}

- (NSString*)firstName
{
    NSString *string = nil;
    [self willAccessValueForKey:@"firstName"];
    string = [self primitiveValueForKey:@"firstName"];
    [self didAccessValueForKey:@"firstName"];
    NSLog(@"Get FIRST NAME");
    return string;
}

Так же можно сделать валидацию значений для проперти, аналогично предыдущему уроку по KVC:

- (BOOL) validateValue:(id  _Nullable __autoreleasing *)value forKey:(NSString *)key error:(NSError * _Nullable __autoreleasing *)error
{
    if ([key isEqualToString:@"lastName"]) {
        *error = [NSError errorWithDomain:@"BAD LAST NAME" code:123 userInfo:nil];
        return NO;
    }
    
    return YES;
}

Можно определить так же ф-ю конкретно для определенного ключа вида:

- (BOOL) validateLastName:(id  _Nullable __autoreleasing *)value error:(NSError * _Nullable __autoreleasing *)error

Если функция валидации возвращает NO, то при сохранении контекста возникнет эта ошибка и объект не будет сохранен.

Исходный код урока.

Домашнее задание по всей CoreData будет в конце 44 урока. 

Комментариев нет:

Отправить комментарий