Core Data для iOS. Глава №4. Теоретическая часть

в 8:44, , рубрики: andrewshmig, core data, iOS, mobile development, objective-c, storage, разработка под iOS, метки: , , , , ,

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

Core Data для iOS. Глава №4. Теоретическая часть

Содержание:

Вступление

Вы можете создавать пользовательские приложения с потрясающим интерфейсом, который помогает им в повседневной жизни и решении проблем, но если вы некорректно представляете данные в приложении, то приложение станет сложно поддерживать, его производительность будет падать и со временем оно может стать совершенно непригодным для использования. В этой главе мы рассмотрим то, каким образом необходимо проектировать модели (данные) для того, чтобы они не портили ваше приложение.

Проектирование базы данных

Американский философ Ралф Уолдо Эмерсон однажды сказал: «A foolish consistency is the hobgoblin of little minds» (Глупое следование чему-то, это удел глупцов). Люди частенько используют это высказывание в целях защиты себя от нападок и вопросов со стороны других к деталям. Надеемся, что мы не попадёмся на эту удочку, хотя мы периодически утверждаем, что Core Data не является базой данных, однако относимся к ней, как к базе данных (автор: что-то вспоминается «если что-то ходит как утка, крякает как утка, значит это утка»). В этой секции рассмотрим, каким образом проектировать структуру данных приложения, а проведение параллели этого процесс к процессу проектированию структуры реляционной базы данных нам очень поможет не только обсуждением, но собственно и конечным результатом. В некоторых точках конечно аналогии не будет существовать, но в ходе дискуссии мы постараемся отметить эти точки.

Сущности в Core Data выглядят, действуют, пахнут и на вкус такие же, как таблицы в реляционных базах данных. Атрибуты — это поля таблицы, связи — JOINы по первичным и внешним ключам, а сами сущности представляют собой те самые записи в таблицах. Если наша модель данных «накладывается» на SQLite тип хранилища, то Core Data собственно реализует всё именно таким образом, который мы описали выше и в главах №2 / №3. Однако не стоит забывать, что ваши данные могут храниться в памяти, или, например, в каком-то файле (атомарном хранилище), и здесь уже нет никаких таблиц и полей, первичных ключей и внешних. Core Data абстрагирует структуру данных от типа используемого хранилища, добиваясь тем самым удобства и универсальности при проектировании и взаимодействии с данными. Позвольте Core Data охватить ваш разум, слейтесь в единое целое с Core Data: либо вы почувствуете всё мощь с использованием Core Data, либо вы окажетесь у разбитого корыта используя неоптимальные структуры данных.

Новички, которые только начинают изучать Core Data, создавая свои первые модели данных, постоянно негодуют, что у них нет возможности создавать и отвечать за первичные ключи в своих сущностях, как они привыкли это делать при традиционном моделировании структуры базы данных. В Core Data нет никаких автоикрементирующихся ключей потому, что они там не нужны. Core Data берет на себя ответственность за создание уникального ключа для каждого добавляемого объекта (managed object). Никаких первичных ключей подразумевает, что нет и никаких внешних ключей: Core Data управляет отношениями между сущностями сама и выполняет любые необходимые joinы. Немедленно прекращайте! Прекращайте ощущать дискомфорт от ненадобности объявлять первичные и внешние ключи!

Еще одним местом, которое может вас затянуть в трясину — моделирование отношений many-to-many. Представьте себе на минуту, что в приложении League Manager игроки могут принадлежать разным командам. Если это было бы так, то у каждой команды могло быть множество игроков, а каждый игрок мог бы играть за разные команды. Если бы вы создавали традиционную реляционную базу данных, то у вас было бы три таблицы: одна для команд, вторая для игроков, а третья для отслеживания какой игрок и каким командам принадлежит. В то же время в Core Data, у те же 2 сущности — Player и Team, а отношение многое-ко-многим настраивается выставлением обычной галочки. Core Data однако «под капотом» по прежнему реализует это с использованием 3 таблиц: ZTEAM, ZPLAYER и Z1TEAMS, но это уже детали реализации. Вам не надо знать, что там будет третья или четвертая таблица, об этом позаботится Core Data, у нее это лучше получится.

Совет: Заботесь о данных, а не о механизмах хранения этих данных.

Нормализация реляционной базы данных

Теория проектирования реляционных баз данных сочетает в себе процесс моделирования данных, называемый нормализацией (процесс нормализации), цель которого уменьшить или полностью исключить избыточность, а так же предоставить эффективный доступ к данным в базе данных.
Процесс нормализации состоит из пяти уровней, или форм, и работа продолжается на 6 уровне. Определения первых пяти форм пожалуй только математикам понравятся, только они их скорее всего и поймут. Выглядят они примерно так:

“A relation R is in fourth normal form (4NF) if and only if, wherever there exists an MVD in R, say A -> -> B, then all attributes of R are also functionally dependent on A. In other words, the only dependencies (FDs or MVDs) in R are of the form K -> X (i.e. a functional dependency from a candidate key K to some other attribute X). Equivalently: R is in 4NF if it is in BCNF and all MVD’s in R are in fact FDs.”

(с) www.databasedesign-resource.com/normal-forms.html

У нас нет ни достаточного количества страниц для описания всех форм, ни желания пройтись по определениям всех нормальны форм и объяснить их суть. Вместо этого, в этой секции будет дано описание каждой нормальной формы по отношению к Core Data и даны некоторые советы по проектированию. Запомните, что следование определенной нормальной форме требует следования всем нормальным формам, которые предшествуют данной.

База данных, которая соответствует первой нормальной форме (1NF) считается нормализованной. Чтобы следовать данной форме (уровню) нормализации, каждая запись в базе данных должна иметь одинаковое количество полей. В контексте Core Data это означает, что любой managed object должен иметь определенное (заранее заданное) кол-во атрибутов. Исходя из того, что Core Data не позволяет вам создавать одну сущность с разным количеством атрибутов, можно сделать вывод, что модели в Core Data автоматически являются нормализованными.

Вторая нормальная форма (2NF) и третья нормальная форма (3NF) имеют отношение к связям между не ключевыми полями и ключевыми, требуя, чтобы не ключевые поля являлись фактами о ключевом поле, которому они принадлежат. Так как в Core Data вы не заботитесь о ключах, то этот вопрос снимается. Однако вы должны убедиться, что все атрибуты сущности описывают именно эту сущность, а не что-то другое. Например, сущность Player не должна иметь атрибут uniformColor, так как цвет формы описывает состояние команды, нежели игрока.

Следующие две нормальные формы, четвертая нормальная форма (4NF) и пятая нормальная форма (5NF) могут считаться теми же формами и в мире Core Data. Они отвечают за уменьшение или исключение избыточности описываемых данных, толкая вас к переносу атрибутов со множественными значениями в отдельные сущности и созданием связей многие-ко-многим между сущностями. В терминах Core Data, 4NF форма гласит, что у сущности не должно быть атрибутов, которые могут иметь несколько значений. Вместо этого, стоит перенести эти атрибуты в отдельную сущность и создать связь многие-ко-многим между сущностями. Рассмотрим на примере нашего приложения с командами и игроками из приложения LeagueManager. Модель данных, которая будет нарушать 4 нормальную форму и 5 нормальную форму, будет состоять из одной сущности, Team, с дополнительным атрибутом — player. Затем мы создадим новые экземпляры Team для каждого игрока, в итоге у нас будет несколько лишних (ненужных) командных объектов. Вместо этого, в модели данных используемой в приложении LeagueManager, создаётся сущность Player, которая объединяется связью «многие-ко-многим» с сущностью Team.

В процессе моделирования не забывайте об этих простых правилах. Например, в модели данных в приложении LeagueManager, атрибут uniformColor в сущности Team представляет собой возможность для осуществления нормализации. Наименования цветов форм представляет собой конечное множество, а значит можно было бы создать дополнительную сущность с именем Color, которая будет иметь атрибут name и которая связана отношением «многие-к-одному» с сущностью Team.

Использование проектировщика моделей в XCode

Некоторые разработчики просто соглашаются использовать те средства разработки, которые им были предоставлены по умолчанию. Другие создают инструменты только тогда, когда текущие кажутся им неполноценными. Другие же упрямо настаивают на создании своих собственным инструментов. Мы подпадаем под последнюю категорию разработчиков, которые используют только те инструменты, которые мы сами сделали. Мы гордимся создаваемыми продуктами, а иногда даже празднуем маленькие победы и удачи по экономии времени на разработку. Мы никогда не считаем затраченное время на разработку своих инструментов потому, что это время входит в разряд «игрового времени» приносящего удовольствие. Однако, как бы удивительно это не звучало, нам никогда в голову не приходило писать собственный проектировщик моделей для Core Data, наверно потому, что XCode итак неплохо справляется с поставленной задачей. Идеальная интеграция в среду разработки, которую мы пожалуй не сможем переплюнуть в своих инструментах. Ко всему прочему, XCode поставляется со встроеным графическим моделировщиком моделей данных, что позволяет намного упростить процесс проектирования. В предыдущих главах нам уже приходилось с ним сталкиваться. В этой секции мы меньше времени будем разбираться о том как работает Core Data, а уделим внимание инструментам, которые частенько будем использовать.

В этой секции никакого кода не будет написано. Вместо этого, мы сконцентрируемся на пользовательском интерфейсе моделировщика данных. Чтобы добавить модель данных в наш проект выберем пункты меню: File -> New -> New FIle, в левой части окна выберите секцию «Core Data». Перед вами появятся три типа файлов: Data Model, Mapping Model, NSManagedObject subclass. Mapping models помогают при миграции данных из одной модели данных в другую (подробнее рассмотрим в Главе №8). Выберите тип Data Model, укажите имя модели данных и сохраните её.
Core Data для iOS. Глава №4. Теоретическая часть

Открывая только что созданный файл модели XCode открывает его в визуальном редакторе моделей. Наличие большого числа кнопок и опций может вас смутить. На рисунке ниже указаны элементы, которые используются при моделировании и дано их описание.
Core Data для iOS. Глава №4. Теоретическая часть

Редактор моделей позволяет вам с легкостью переключаться между сущностями, свойствами, отношениями (связями), запросами и конфигурациями модели данных. Так же есть возможность переключаться между вариантами отображения моделей данных: представление в виде графа, либо табличный вид.
Core Data для iOS. Глава №4. Теоретическая часть

Чтобы было интереснее обсуждать модель данных, предлагаю открыть модель, которую мы разработали в приложении League Manager.
Core Data для iOS. Глава №4. Теоретическая часть

Как мы видим, по умолчанию XCode отображает сущности в виде таблиц. Если вам удобнее работать с графическим представлением моделей, то необходимо нажать на кнопку «Graph View» находящуюся над надписью «Editor Style» в нижней правой части редактора.
Core Data для iOS. Глава №4. Теоретическая часть

Графическое представление должно быть знакомо проектировщиками и является чем-то стандартным, описывающим сущности и связи между ними. Вы, наверно, уже заметили, что связь от сущности Team к Player с двумя стрелочками. Это потому, что связь представляет собой связь типа один-ко-многим. У команды может быть несколько игроков. Обратная связь от Player к Team является связью типа один-к-одному и соответственно обозначается одной стрелочкой. Игрок может принадлежать только одной команде.
По опыту использования визульного проектировщика моделей можем с уверенностью сказать, что редактирование сущностей и атрибутов намного удобней в табличном виде, графическое представление хорошо для визуализации связей между ними. Однако у вас может быть свой подход.
Получить больше информации о свойстве или сущности можно выбрав его/её. На рисунке, который представлен ниже, например, выбрана сущность Player. В верхней правой части можно видеть детали о сущности Player; её имя, её класс NSManagedObject, отсутствующий родительский класс, не абстрактный класс. Выбирая свойство, панель изменяет свой вид для отображения свойств свойств. Если мы выберем атрибут email, то панель изменит свой виде следующим образом:
Core Data для iOS. Глава №4. Теоретическая часть

+ Называется атрибут email
+ Является опциональным
+ Не является временным или индексируемым
+ Типа String
+ Нет ограничения на минимальную и максимальную длину
+ Нет значения по умолчанию
+ Нет привязанных регулярных выражений
+ Поле не индексируется Spotlightом
+ Значение поля не будет храниться во внешней записи

Переключаясь на связь team сущности Player изменяет панель следующим образом:
Core Data для iOS. Глава №4. Теоретическая часть

+ Называется атрибут team
+ Конечной (связанной) сущностью является сущность Team
+ Есть обратная связь и называется она players
+ Является опциональной, но не временной
+ Не является связью типа один-ко-многим
+ Минимальное и максимальное число равно 1
+ Используется правило удаления Nullify
+ Поле не индексируется Spotlightом
+ Значение поля не будет храниться во внешней записи

Взглянем на сущность Player:
Core Data для iOS. Глава №4. Теоретическая часть

Сущности определяются своими именами. Core Data позволяет унаследовать от NSManagedObject для того, чтобы предоставить альтернативную реализацию managed objectов. Если вы создадите подкласс NSManagedObject класса и захотите связать его с определенной сущностью, тогда вам необходимо будет указать наименование нового класса в поле «Class» раздела «Entity» (изображение выше).

Просмотр и редактирование атрибутов

Выберите любой атрибут в секции «Атрибуты» в XCode для того, чтобы правая панель изменила своё представление и отобразила всю информацию по выбранному атрибуту. В таблице ниже дано описание наименования свойства атрибута и его предназначение.

Наименование Описание
Name Наименование атрибута
Transient Говорит Core Data не сохранять данный атрибут. Полезно использовать данный тип свойства совместно со вторым атрибутом для поддержки нестандартных или настраиваемых типов. Настраиваемые типы будут рассмотрены чуть позже в этой главе.
Optional Опциональный атрибут может принимать значение nil. Неопциональные атрибуты должны принимать любое не nil значение.
Indexed По индексированым атрибутам быстрее искать, но они занимают больше места в хранилище.
Attribute Type Тип атрибута. В зависимости от выбранного типа будут отображены те или иные поля валидации (проверки).
Validation Используется для установки минимальной и максимальной длины при выбранном типе String, или минимального/максимального значения при выбранном целочисленном типе.
Min Length Чекбокс для включения проверки минимального значения длины
Max Length Чекбокс для включения проверки максимального значения длины
Minimum Чекбокс для включения проверки минимального значения
Maximum Чекбокс для включения проверки максимального значения
Default value Значение по умолчанию для данного атрибута в случае, если не было указано значение.
Reg. Ex. Регулярное выражение для проверки (валидации) данных
Просмотр и редактирование связей

Таким же образом, как и при просмотре информации об атрибуте, мы можем просмотреть свойства связи выбрав любую связь.

Связи обладают несколькими свойствами. Во-первых, у них есть имя и конечная сущность (сущность в которую входит связь). Конечная сущность описывает тип объекта на который указывает связь. Для сущности Player, связь «team», как полагается, указывает на сущность Team. Архитекторы Core Data строго-настрого советуют указывать у каждой связи её обратную связь (inverse relashionship) — связь, которая направлена в обратную сторону. Например, сущность Player имеет связь с Team, значит исходя из рекомендации, как мы и реализовали, Team так же должна иметь связь с Player. По факту, если вы забудете или не захотите устанавливать обратную связь, то компилятором будет сгенерировано предупреждение. Дополнительные два свойства доступны для атрибутов: Transient и Optional. Transient связь не сохраняется в хранилище во время операции сохранения. Если бы team связь была типа Transient, то при сохранении и последующем перезапуске приложения терялась бы информации о том, какой команде принадлежит игрок. Объект Player по прежнему бы сохранялся, но его связь с объектом Team нет. Transient связи могут быть полезны тогда, когда у вас есть необходимость установить значения во время выполнения, например, пароля или чего-то такого, что получается из информации получаемой во время работы приложения. Если же связь Optional, то это просто означает, что можно установить её в значение nil. Например, у игрока может не быть команды.

Множественность связи определяет кол-во конечных объектов, к которым исходный объект может относиться: к-одному или ко-многим. По умолчанию связь является к-одному, что означает, что исходный объект может относиться лишь к одному конечному. Это как раз случай в примере с team к сущности Player: у игрока может быть не более одной команды. Однако команда может иметь множество игроков, именно поэтому связь player в сущности Team имеет связь ко-многим. Эти значения (со стороны ко-многим) представляются в виде множества (NSSet) объектов. Множественность связи может быть также определена путём изменения минимальных и максимальных значений, которые представляют собой мощность конечного множества сущностей в связи.

В завершение, Core Data использует правила удаления связей для того, чтобы знать, что делать с конечными объектами при удалении исходного. Есть множество вариантов удаления, которые Core Data поддерживает. Присутствуют такие виды удалений: No action, Nullify, Cascade, Deny. Секция «Правила настройки» в этой главе, как раз и обсуждает правила удаления и их значение.

Использование запросов в качестве свойств

До сих пор в книге мы упоминали об атрибутах и связях уже несколько раз. Третее свойство, которым может обладать сущность: запрос (выборка). Запросы в качестве свойств, сравнимы со связями в том, что могут ссылаться на другие объекты. Если свойство сущности типа «Связь» ссылается напрямую на конечные объекты, то «Запросы» (тоже свойство сущности) ссылаются на объекты выбранные указанным предикатом. Запросы в качестве свойств представляют собой своего рода «умные» списки проигрывания в iTunes, в котором пользователь указывает музыкальную библиотеку, а затем фильтрует содержимое по определенным критериям. Хотя запросы в качестве свойств и не ведут себя настолько одинаково с выборкой в iTunes, они вычисляются единожды при первом запросе, результаты этой выборки кэшируются до тех пор, пока мы сами не скажем, что надо обновиться, вызвав метод refreshObject:mergeChanges:.

В League Manager приложении мы можем, например, создать свойство запрос, которое будет возвращать всех игроков из команды, чьи имена начинаются на «W». Выберите сущность Team, нажмите кнопку "+" в разделе «Fetched Properties» и назовите новый запрос wPlayers. Выберите сущность Player в качестве конечной (тип сущности, которая будет запрашиваться и возвращаться).

В поле «Predicate» укажите критерий для фильтра используя стандартный формат NSPredicate (подробнее о формате и NSPredicate поговорим в 6 главе). В указанном примере предикат будет выглядеть следующим образом: lastName BEGINSWITH "W".

После этого, созданный нами именной запрос, станет таким же атрибутом объекта Player, как и атрибут имени. Использовать его можно таким же образом, как и прочие атрибуты. Можно убедиться в корректности работы добавленного запроса, добавив код в метод делегата League Manager application:didFinishLaunchingWithOptions:, который будет использовать wPlayer атрибут. Код, который показан ниже запрашивает все команды, берет первую команду (если таковая существует) и выбирает всех игроков согласно указанному предикату в wPlayer атрибуте. После чего все игроки удовлетворяющие условию выводятся в консоль (их имена и фамилии).

NSFetchedRequest *fetchRequest = [[NSFetchedRequest alloc] init];
[fetchRequest setEntity:[NSEntityDescription entityForName:@"Team" inManagedObjectContext:self.managedObjectContext]];
NSArray *teams = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];

if([teams count] > 0){
   NSManagedObject *team = [teams objectAtIndex:0];
   NSSet *wPlayers = [team valueForKey:@"wPlayers"];

   for(NSManagedObject *wPlayer in wPlayers) {
      NSLog(@"%@ %@", [wPlayer valueForKey:@"firstName"], [wPlayer valueForKey:@"lastName"]);
   }
}

Если мы запустим данный код на выполнение, в зависимости от того, какие данные у вас в приложении уже есть, мы должны увидеть будем примерно следующий вывод:

2011-07-31 19:08:23.005 League Manager[8039:f203] Dwyane Wade 
2011-07-31 19:08:23.006 League Manager[8039:f203] Tyson Warner

Последний важный момент, который необходимо рассмотреть, это создание запросов в качестве свойств в XCode. Мы можем использовать запросы в качестве свойств таким же образом, как используем NSFetchRequests в приложении, с одной лишь разницей в том, что запросы в качестве свойств задаются заранее и в редакторе моделей. Для создание нового запроса в качестве свойства, который будет выбирать все команды с зеленым цветом формы, например, выберем сущность Team в разделе «Сущности», удерживайте кнопку «Add Entity» до тех пор, пока не появится выпадающий список, выберите из этого списка опцию «Add Fetch Request». Назовем этот запрос «GreenTeams». В средней колонке в Xcode вы должны увидеть кнопку "+" для добавления нового критерия фильтрации. Нажмите на кнопку "+" и введите в текстовое поле строку: uniformColor == «Green». Всё должно выглядеть примерно следующим образом:
Core Data для iOS. Глава №4. Теоретическая часть

Запросы в качестве свойств созданные таким образом хранятся в модели данных и могут быть получены при помощи метода fetchRequestTemplateForName: класса NSManagedObjectModel. Добавим немного кода в метод делегата application:didFinishLaunchingWithOptions: для проверки работоспособности новоиспеченного запроса:

NSFetchRequest *fetchRequest = [self.managedObjectModel fetchRequestForTemplateName:@"GreenTeams"];
NSArray *teams = [self.managedObjectContext executeFetchRequest:fetchRequest error:nil];

for(NSManagedObject *team in teams){
   NSLog(@"%@", [team valueForKey:@"name"]);
}

В зависимости от данных, которые у вас в приложении, вы должны увидеть примерно следующий вывод:

2011-07-31 19:17:25.598 League Manager[8184:f203] Boston Celtics
Создание сущностей

До сих пор мы очень активно обсуждали сущности. Сущности описывают атрибуты и связи, которыми обладает объект (managed object). Для того, чтобы добавить новую сущность, необходимо нажать на кнопку "+" с надписью «Add Entity» и указать имя.

По умолчанию, экземпляры объектов сущностей представлены типом NSManagedObject, однако при увеличении размеров и сложности проекта, может возникнуть необходимость создать свои классы, которые будут представлять managed объекты. Создаваемые вами managed объекты должны наследоваться от NSManagedObject класса, имя созданного класса необходимо указать будет в поле «Class» при редактировании сущности.
Core Data для iOS. Глава №4. Теоретическая часть

Одну темы мы еще не затрагивали на данном этапе — наследование сущностей. Механизм наследования сущностей похож на механизм наследования классов, когда создаваемые атрибуты/методы наследуются. Например, вы можете создать сущность Person, а сущность Player унаследовать от Person. Если бы в приложении были другие представления людей, например, тренера, то сущность Coach можно было тоже унаследовать от Person. Если бы мы захотели запретить программисту непосредственно создавать экземпляр сущности Person, то нам достаточно указать, что данная сущность является абстрактной. Для примера, создадим сущность Person с полем dateOfBirth и отметим её, как абстрактную сущность.
Core Data для iOS. Глава №4. Теоретическая часть

Следующим шагом будет изменение сущности Player: установка родительской (наследуемой) сущности Person. Выберите сущность Player и в выпадающем списке под названием «Parent Entity» выберите «Person»:
Core Data для iOS. Глава №4. Теоретическая часть

Теперь у сущности Player будет доступен атрибут dateOfBirth.
Core Data для iOS. Глава №4. Теоретическая часть

Создание атрибутов

Атрибуты описывают сущность. Атрибуты сущности описывают текущее состояние сущности и могут быть представлены различными типами данных. Выберите любую сущность, в секции «Attributes» нажмите на "+" (добавление нового атрибута, не забудьте указать имя). Рисунок ниже показывает возможные настройки атрибута:
Core Data для iOS. Глава №4. Теоретическая часть

Ранее в этой главе мы уже говорили о различных свойствах атрибутов. Бесспорно самым важным свойством атрибута является его тип. По умолчанию Core Data предоставляет несколько типов:
Core Data для iOS. Глава №4. Теоретическая часть
Core Data для iOS. Глава №4. Теоретическая часть

Большинство перечисленных выше типов напрямую связаны с типами данных в Objective-C. Единственным типом, который выпадает из общей картины является тип Transformable, который предназначен для настраиваемых (создаваемых программистом) типов. Рекомендуется использовать Transformable тип в тех случаях, когда ни один из существующих типов не может содержать требуемые вам данные. В качестве примера можно взять опять таки наше приложение League Manager, в котором необходимо хранить цвета, которые можно представить типом CGColorRef. В этом случае атрибут должен быть Transient и Transformable, а в тоже время мы должны будем представить Core Data механизм преобразования атрибута в уже существующий тип. Используйте настраиваемые (кастомные) атрибуты в тех случаях, когда вы создаете свой класс, который наследуется от NSManagedObject.

Создание связей

В процессе нормализации, вы скорее всего будете создавать несколько сущностей, в зависимости от сложности модели данных. Связи позволяют связать сущности между собой, как это сделано в League Manager приложении с сущностями Player и Team. Core Data позволяет тюнинговать (каюсь, вот прям нравится это слово) связи, чтобы те в свою очередь, наиболее точно отражали отношения между моделируемыми данными.

В Xcode при создании связи или при выборе оной, мы видим набор настроек, которые позволят нам изменить природу связи. Например, если мы выберем связь team в сущности Player, что увидим примерно следующую картину:
Core Data для iOS. Глава №4. Теоретическая часть

Список полей:

  • Имя
  • Конечная сущность
  • Обратная связь
  • Переходный (Transient)
  • Опциональна
  • Является ли связь «ко-многим»
  • Кол-во (минимум и максимум)
  • Правила удаления

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

Наименование связи (Name)

Первое поле, Name, становится наименование атрибута NSManagedObject объекта относящегося к связи. По принятым соглашениям, наименование должно быть в нижнем регистре (lowercase). Если же связь является «ко-многим», то наименование должно принимать вид во мн. числе. Стоит понимать, что всё это лишь соглашения, следование которым позволит сделать ваши модели более понятными, читаемыми и легко поддерживаемыми. Можно заметить, что в приложении League Manager мы придерживаемся соглашений, поэтому связь в сущности Player называется team. В сущности же Team связь с игроками называется «players».

Конечные и обратные сущности (Destination and Inverse)

Следующее поле, Destination, определяет сущность с другого конца связи. Inverse поле позволяет выбрать ту же связь, но только со стороны конечной сущности.

Оставляя Inverse поле в значении No Inverse Relationship гарантирует вам получение двух предупреждение: ошибка согласованности и неправильно настроенный атрибут.

Вы можете не обращать внимания, в конце концов это всего лишь предупреждения компилятора, а не ошибки, однако вы можете столкнуться с неожиданными последствиями. Core Data использует двухстороннюю информацию о связи для поддержания согласованности объектного графа для обработки операции Redo/Undo. Оставляя связь без указания Inverse сущности, вы берете на себя ответственность за поддержания согласованности объектного графа для выполнения операций Undo/Redo. Документация Apple строго настрого не советует этого делать, в частности в случае использовать связь «ко-многим». В случае, если вы не указываете обратную (Inverse) связь, то managed объект с другого конца связи не будет помечен, как измененный объект, если managed объект с этой стороны связи изменился.

Переходный (Transient)

Помечая связь, как «Transient» вы указываете Core Data, что эту связь не надо сохранять в хранилище. Переходная связь всё еще поддерживает операции Redo/Undo, но исчезает при закрытии приложения. Вот несколько возможных вариантов использования переходной связи:

  • Связи между сущностями, которые являются временными и не должны существовать дольше, чем текущая сессия запуска приложения
  • Информация о связи, которая может быть получена из каких-то внешних источников данных, будь-то другое Core Data хранилище или какой-то другой источник информации

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

Опциональный (Optional)

Следующее поле, Optional, является чекбоксом, который определяет, может ли связь принимать значение nil или нет. Считайте, что это что-то вроде NULL или не-NULL значения в поле базы данных. Если чекбокс установлен, то сущность возможно будет сохранить без указания связи. Если же попытаться сохранить сущность со сброшенным чекбоксом, то сохранение закончиться неудачей. Если в сущности Player в приложении League Manager оставить связь team со сброшенным чекбоксом, то каждый игрок должен будет принадлежать какой-то команде. Установка связи team в nil и дальнейшем сохранении (вызове метода save:) приведет к ошибке с текстом «team is a required value».

Связь «ко-многим» (To-many)

Опция так же представляет собой чекбокс. Если чекбокс установлен, то текущая связь может указывать на множество объектов на том конце, если же нет, то получаем связь типа «к-одному».

Количество (минимальное и максимальное)

Определяет кол-во объектов на той стороне связи, которые может иметь текущий объект. Опция действует только в случае, если выбрана связь типа «ко-многим».
Превышение лимита при сохранении выдаст ошибку «Too many items», а недостаточное кол-во — «Too few items».
Кстати, минимальное значение может быть больше максимального, что в целом устроит Core Data, так как проверки не производится. Поэтому в случае, если минимальное значение будет больше максимального, любая попытка сохранить объектный граф провалится.

Правила удаления (Delete Rule)

Правила удаления определяют действия, которые необходимо осуществить Core Data при удалении объекта со связями.
Существуют 4 типа удаления:
1. Cascade — исходный объект удаляется, все связанные (конечные) объекты удаляются тоже
2. Deny — если исходный объект связан с какими-то объектами, то удаления не происходит
3. Nullify — исходный объект удаляется, у всех связанных объектов обратная связь устанавливается в nil.
4. No Action — исходный объект удаляется, связанные объекты никак не меняются

В приложении League Manager, правило удаления для связи player является Cascade, что значит, что при удалении команды все её игроки будут так же удалены. Если бы мы изменили правило удаления на Deny, то удаления не произошло бы в случае, если у команды был бы хотя бы один игрок. Установка же правила удаления в Nullify привела к тому, что все игроки остались бы в хранилище, но с обнуленной ссылкой на команду, в то время, как команда сама была бы удалена.

Автор: AndrewShmig

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js