...

суббота, 31 августа 2013 г.

Pro Core Data for iOS. Глава №2. Практическая часть

Хабралюди, добрый день!

Сегодня хочу начать вольный перевод книги Михаеля Привата и Роберта Варнера «Pro Core Data for iOS», которую можете скачать по этой ссылке. Каждая глава будет содержать теоретическую и временами практическую часть.

image


Содержание:



  • Глава №1. Приступаем (Практическая часть)

  • Глава №2. Усваиваем Core Data (Практическая часть)

  • Глава №3. Хранение данных: SQLite и другие варианты

  • Глава №4. Создание модели данных

  • Глава №5. Работаем с объектами данных

  • Глава №6. Обработка результатирующих множеств

  • Глава №7. Настройка производительности и используемой памяти

  • Глава №8. Управление версиями и миграции

  • Глава №9. Управление таблицами с использованием NSFetchedResultsController

  • Глава №10. Использование Core Data в продвинутых приложениях





Вступление



Будем мы создавать вот такую модель:

image

Затем добавим несколько записей и запросим их. Вывод будет производиться в консоль, чтобы мы не задавались еще и вопросами визуальными.

Готовы? Тогда поехали!


Описание



Будем создавать объектный граф нашего любимого ресурса — Хабра.

Есть N основных объектов:


  • Запись в блоге

  • Пользователь

  • Теги

  • Хабы

  • Вопросы

  • Ответы

  • Учетная запись компании




Этого будет достаточно.

Какие данные содержит каждый из объектов?



  • Запись в блоге — заголовок, текст

  • Пользователь — ник, карма, рейтинг, пол, аватар, почтовый ящик

  • Тег — наименование

  • Хаб — наименование, профильный ли хаб

  • Вопрос — заголовок, текст

  • Ответ — текст

  • Учетная запись компании — наименование организации, рейтинг


Приступаем к работе



Открываем XCode и создаём новый проект Single View Application:


Вводим наименование проекта, префиксы и прочее:

image


Знакомое окно:

image


Добавляем Core Data



Добавляем Core Data Framework в проект:

image
Создаем модели



Добавить новый файл -> iOS -> CoreData -> Data Model

image

image

Начнём с записи в блоге.

Создаем новую сущность и называем её Blogpost, добавляем два атрибута caption (String) и text (String).

image


Создаем новую сущность и называем её User, добавляем атрибуты avatar (String), email (String), gender (Decimal), karma (Integer 16), nickname (String), rating (Integer 16).

image


Создаем новую сущность и называем её Tag, добавляем всего один атрибут name (String).

image


Создаем новую сущность и называем её Hab, добавляем два атрибута name (String), target (Boolean).

image


Создаем новую сущность и называем её Question, добавляем два атрибута caption (String), text (String).

image


Создаем новую сущность и называем её Response, добавляем один атрибут text (String).

image


Создаем новую сущность и называем её Organization, добавляем два атрибута name (String), rating (Integer 16).

image


Вот что у нас есть в итоге:

image


Теперь займемся установкой отношений между объектами.

У учетной записи компании есть множество пользователей (сотрудников).

У пользователя есть множество записей в блоге, у записи в блоге есть множество тегов и хабов.

У пользователя есть множество вопросов, а у вопроса множество ответов.


Начнем с построение связи «учетная запись компании» (один-ко-многим) «пользователи».

Выбираем из списка сущностей Organization и добавляем новую связь нажав на "+" в разделе Relationships:

image


Так как по умолчанию XCode создаёт связь один-к-одному мы должны изменить тип связи:

image


Теперь у каждой организации есть множество пользователей. Поле Inverse (обратная связь) мы сейчас тоже установим, но предварительно добавим новую связь в сущность Пользователя и назовем её organization (организация в которой работает пользователь, а если он не работает нигде, то поле будет null):

image


Теперь вновь откроем редактирование сущности Organization и установим поле Inverse в organization:

image


Вот как стал выглядеть теперь объектный граф:

image


Попробуйте расставить остальные связи сами, а затем сверьте с тем, что у меня получилось.

Вы знаете как создать связь, вы знаете как изменить тип связи с один-к-одному на один-ко-многим — этого будет достаточно.

Итоговая картина:

image


Создаем организацию



Уже имеем в AppDelegate.h такое:

//
// TMAppDelegate.h
// Habrahabr
//
// Created by AndrewShmig on 8/31/13.
// Copyright (c) 2013 TM. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>

@class TMViewController;

@interface TMAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) TMViewController *viewController;

@property (nonatomic, strong) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, strong) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;

@end




И в AppDelegate.m:

//
// TMAppDelegate.m
// Habrahabr
//
// Created by AndrewShmig on 8/31/13.
// Copyright (c) 2013 TM. All rights reserved.
//

#import "TMAppDelegate.h"

@implementation TMAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
return YES;
}

#pragma mark - Core Data Stack

- (NSManagedObjectModel *)managedObjectModel
{
if(nil != _managedObjectModel)
return _managedObjectModel;

_managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:nil];

return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if(nil != _persistentStoreCoordinator)
return _persistentStoreCoordinator;

NSURL *storeURL = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:@"Habrahabr.sqlite"];

NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
if(![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]){
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}

return _persistentStoreCoordinator;
}

- (NSManagedObjectContext *)managedObjectContext
{
if(nil != _managedObjectContext)
return _managedObjectContext;

NSPersistentStoreCoordinator *store = self.persistentStoreCoordinator;
if(nil != store){
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:store];
}

return _managedObjectContext;
}

@end




Перепишем метод application:didFinishLaunchingWithOptions: таким образом:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Создаем организацию
NSManagedObject *yandex = [NSEntityDescription insertNewObjectForEntityForName:@"Organization"
inManagedObjectContext:self.managedObjectContext];
[yandex setValue:@"Yandex Inc." forKey:@"name"];
[yandex setValue:@672 forKey:@"rating"];

// создаем сотрудников Yandex
NSManagedObject *pupkin = [NSEntityDescription insertNewObjectForEntityForName:@"User"
inManagedObjectContext:self.managedObjectContext];
[pupkin setValue:@"VaseaPup" forKey:@"nickname"];
[pupkin setValue:@"vasilisa@yandex.ru" forKey:@"email"];
[pupkin setValue:@1 forKey:@"gender"]; // 0 - unknown, 1 - male, 2 - female
[pupkin setValue:@0 forKey:@"karma"];
[pupkin setValue:@0 forKey:@"rating"];

NSManagedObject *gosha = [NSEntityDescription insertNewObjectForEntityForName:@"User"
inManagedObjectContext:self.managedObjectContext];
[gosha setValue:@"Goshka" forKey:@"nickname"];
[gosha setValue:@"gosha.k@yandex.ru" forKey:@"email"];
[gosha setValue:@0 forKey:@"gender"];
[gosha setValue:@0 forKey:@"karma"];
[gosha setValue:@0 forKey:@"rating"];

// связываем сотрудников с компанией
NSMutableSet *employees = [yandex mutableSetValueForKey:@"employees"];
[employees addObject:pupkin];
[employees addObject:gosha];

// сохраняем данные в хранилище
[self saveContext];

NSLog(@"Finish!");

return YES;
}




Запустим приложение. Данные должны были быть сохранены. Проверим это.

Найдем файл Habrahabr.sqlite:

image

Запустим терминал и проверим структуру БД:



AndrewShmigs-MacBook-Pro:~ new$ cd "/Users/new/Library/Application Support/iPhone Simulator/6.1/Applications/95B0716A-9C2C-4BD8-8117-62FB46BB5879"
AndrewShmigs-MacBook-Pro:95B0716A-9C2C-4BD8-8117-62FB46BB5879 new$ ls
Documents Habrahabr.app Library tmp
AndrewShmigs-MacBook-Pro:95B0716A-9C2C-4BD8-8117-62FB46BB5879 new$ cd Documents/
AndrewShmigs-MacBook-Pro:Documents new$ ls
Habrahabr.sqlite
AndrewShmigs-MacBook-Pro:Documents new$ sqlite3 Habrahabr.sqlite
SQLite version 3.7.12 2012-04-03 19:43:07
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .schema
CREATE TABLE ZBLOGPOST ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZAUTHOR INTEGER, ZCAPTION VARCHAR, ZTEXT VARCHAR );
CREATE TABLE ZHAB ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZTARGET INTEGER, ZBLOGPOSTS INTEGER, ZNAME VARCHAR );
CREATE TABLE ZORGANIZATION ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZRATING INTEGER, ZNAME VARCHAR );
CREATE TABLE ZQUESTION ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZAUTHOR INTEGER, ZCAPTION VARCHAR, ZTEXT VARCHAR );
CREATE TABLE ZRESPONSE ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZQUESTION INTEGER, ZTEXT VARCHAR );
CREATE TABLE ZTAG ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZBLOGPOST INTEGER, ZNAME VARCHAR );
CREATE TABLE ZUSER ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZKARMA INTEGER, ZRATING INTEGER, ZORGANIZATION INTEGER, ZGENDER DECIMAL, ZAVATAR VARCHAR, ZEMAIL VARCHAR, ZNICKNAME VARCHAR );
CREATE TABLE Z_METADATA (Z_VERSION INTEGER PRIMARY KEY, Z_UUID VARCHAR(255), Z_PLIST BLOB);
CREATE TABLE Z_PRIMARYKEY (Z_ENT INTEGER PRIMARY KEY, Z_NAME VARCHAR, Z_SUPER INTEGER, Z_MAX INTEGER);
CREATE INDEX ZBLOGPOST_ZAUTHOR_INDEX ON ZBLOGPOST (ZAUTHOR);
CREATE INDEX ZHAB_ZBLOGPOSTS_INDEX ON ZHAB (ZBLOGPOSTS);
CREATE INDEX ZQUESTION_ZAUTHOR_INDEX ON ZQUESTION (ZAUTHOR);
CREATE INDEX ZRESPONSE_ZQUESTION_INDEX ON ZRESPONSE (ZQUESTION);
CREATE INDEX ZTAG_ZBLOGPOST_INDEX ON ZTAG (ZBLOGPOST);
CREATE INDEX ZUSER_ZORGANIZATION_INDEX ON ZUSER (ZORGANIZATION);
sqlite> select * from ZORGANIZATION;
1|3|1|672|Yandex Inc.
sqlite> select * from ZUSER;
1|7|1|0|0|1|0||gosha.k@yandex.ru|Goshka
2|7|1|0|0|1|1||vasilisa@yandex.ru|VaseaPup
sqlite>




Добавим теперь одному из сотрудников вопрос и создадим какой-то пост.

// добавляем сотруднику Гоше вопрос
NSManagedObject *whoAmI = [NSEntityDescription insertNewObjectForEntityForName:@"Question"
inManagedObjectContext:self.managedObjectContext];
[whoAmI setValue:@"Who am I?" forKey:@"caption"];
[whoAmI setValue:@"Помогите мне узнать себя лучше." forKey:@"text"];

NSMutableSet *goshasQuestions = [gosha mutableSetValueForKey:@"questions"];
[goshasQuestions addObject:whoAmI];




Запустим приложение и проверим БД:

sqlite> select * from ZQUESTION;
1|4|1|4|Who am I?|Помогите мне узнать себя лучше.
sqlite>




Добавим блогпост сотруднику Васе Пупкину:

// добавляем сотруднику Васе Пупкину новый блогпост
NSManagedObject *newPost = [NSEntityDescription insertNewObjectForEntityForName:@"Blogpost"
inManagedObjectContext:self.managedObjectContext];
[newPost setValue:@"yandex.Деньги & yandex.Карты & yandex.ДваСтвола" forKey:@"caption"];
[newPost setValue:@"Some text" forKey:@"text"];

// добавляем два Хаба
NSManagedObject *hab1 = [NSEntityDescription insertNewObjectForEntityForName:@"Hab"
inManagedObjectContext:self.managedObjectContext];
[hab1 setValue:@"iOS" forKey:@"name"];
[hab1 setValue:@YES forKey:@"target"];

NSManagedObject *hab2 = [NSEntityDescription insertNewObjectForEntityForName:@"Hab"
inManagedObjectContext:self.managedObjectContext];
[hab2 setValue:@"Objective-C" forKey:@"name"];
[hab2 setValue:@YES forKey:@"target"];

NSMutableSet *habs = [newPost mutableSetValueForKey:@"habs"];
[habs addObject:hab1];
[habs addObject:hab2];

[[pupkin mutableSetValueForKey:@"blogposts"] addObject:newPost];




И вывод:

sqlite> select * from ZBLOGPOST;
1|1|1|5|yandex.Деньги & yandex.Карты & yandex.ДваСтвола|Some text
sqlite> select * from ZHAB;
1|2|1|1|1|iOS
2|2|1|1|1|Objective-C
sqlite>


Вывод данных



Выведем всех сотрудников и наименования компаний в которых они работают. Код для чтения данных будем писать в том же методе в котором писали и запись данных.

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"User"];
NSArray *allUsers = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];

for(NSManagedObject *user in allUsers){
NSString *nickname = [user valueForKey:@"nickname"];
NSString *organization = [user valueForKeyPath:@"organization.name"];

NSLog(@"%@ works at %@", nickname, organization);
}




Получим примерно такой вывод (приложение у меня запускалось несколько раз и данные тоже несколько раз были добавлены):

2013-08-31 13:00:27.255 Habrahabr[18148:c07] Goshka works at Yandex Inc.
2013-08-31 13:00:27.257 Habrahabr[18148:c07] VaseaPup works at Yandex Inc.
2013-08-31 13:00:27.258 Habrahabr[18148:c07] VaseaPup works at Yandex Inc.
2013-08-31 13:00:27.258 Habrahabr[18148:c07] Goshka works at Yandex Inc.
2013-08-31 13:00:27.259 Habrahabr[18148:c07] VaseaPup works at Yandex Inc.
2013-08-31 13:00:27.259 Habrahabr[18148:c07] Goshka works at Yandex Inc.
2013-08-31 13:00:27.260 Habrahabr[18148:c07] Finish!




Обратите внимание на то, как мы получили наименование организации в которой работает пользователь. Как Вам? А? Я так и думал, что понравится!
В завершение



Экспериментируйте! Не бойтесь, если что-то сломается, полный проект находится по этой ссылке.

Удачи и благодарю за внимание!

Надеюсь вам понравилась практическая часть.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



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

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