Core Data в iOS 9 и Swift при ограничениях на уникальность. Часть 1.

Screen Shot 2016-04-07 at 5.00.29 PM

Продолжая развивать идею о дополнении курса «Developing iOS 8 Apps with Swift» 2015 г.  некоторыми важными темами, давайте рассмотрим использование Core Data при создании приложений в  iOS 9 на Swift.
Core Data — один из наиболее мощных фреймворков в iOS 9. Фундаментально, это способ создания Swift (Objective-C)  объектов как объектов, “отображенных” в SQL или XML базах данных. Это своего рода “мостик” между объектно-ориентированной “территорией” и “территорией” баз данных. В качестве базы данных доминирует SQLite.


Для такого “отображения”, как и во всякой другой базе данных, мы создаем в Xcode Модель Данных (Data Model) и там работаем с:
Сущностями (Entities) — в “мире” баз данных это таблицы, в которых будут “отображаться” наши Swift объекты,
Атрибутами (Attributes) — это колонки в таблице базы данных. В нашем объектно-ориентированном “мире”  это соответствует свойствам (properties) объектов.
Взаимосвязями (Relationships) — это также свойства объектов, но они являются указателями на другие объекты в базе данных, или указателями на ряд других объектов, это что-то типа “joins” между таблицами в базе данных.
Запросами в качестве свойств (Fetch properties) — это “вычисляемый” способ получить указатель на некоторые другие свойства. Если Взаимосвязи ссылаются напрямую на конечные объекты, то Запросы ссылаются на объекты, выбранные указанным предикатом.
Как нам получить доступ ко всему этому в нашем Swift коде после того, как мы создали  Модель Данных (Data Model) ? Ответ состоит в том, что нам нужен контекст NSManagedObjectContext (для краткости в дальнейшем обозначим его MOC). Этот MOC в коде является “центральным пространством” для создания объектов в базе данных, установления их атрибутов и запросов к объектам. Все это мы будем делать через MOC. Он же будет автоматически “отображать” наши действия в SQLite.

При использовании Core Data в приложениях iOS нужно решить два принципиальных вопроса:

  • как получить контекст MOC — концентратор любой активности Core Data
  • как правильно распространить его использование в View Controllers, составляющих пользовательский интерфейс

Существует два способа получения контекста MOC:

  1. Напрямую инициализировать контекст MOC
  2. Создать UIManagedDocument и запросить у него свойство managedObjectContext как контекст

В первом способе вам необходимо определить файл для постоянного хранения базы данных, назначить PersistentStoreCoordinator, добавить вашу Модель Данных (Data Model) в эту общую картину. То есть получить так называемый Core Data Stack. И все это вручную. Но можно воспользоваться определенными шаблонами при создании нового проекта, в которых есть опция  “Use Сore Data”. После создания проекта c включенной опцией “Use Сore Data” вы обнаружите огромное количество кода Core Data в AppDelegate, который проделывает все необходимые подготовительные операции для создания контекста MOC.
Второй способ предполагает создание документа — экземпляра класса  UIManagedDocument, у которого есть свойство managedObjectContext — нужный нам контекст  MOC. В этом случае UIManagedDocument берет на себя связь с SQLite, вам даже не нужно принудительно сохранять контекста MOC — документ использует режим “автосохранения” (AUTOSAVE).
В этой статье мы рассмотрим оба способа и выясним их преимущества и недостатки.
Однако, начиная с iOS 5, когда стали доступны решения на основе множества контекстов MOC, связанных отношением Parent-Child и когда каждый контекст MOC смог иметь свою собственную Grand Central Dispatch (GCD) очередь, привлекательность UIManagedDocument снизилась, хотя по-прежнему он остается одним из самых простых способов получения контекста MOC.
Мы рассмотрим в статье три случая получения контекста MOC :

  1. Классический Core Data Stack на основе одного контекста MOC
  2. MOC, полученный с помощью UIManagedDocument
  3. Немного более сложный Core Data Stack на основе обязательных 2-х Parent-Child контекстов MOC: Private Queue Context (“writer”) и Main Queue Context (UI) и возможных рабочих private контекстов MOC. Этот Core Data Stack популяризируется экспертом Marcus Zara в своих статьях и выступлениях.

Что касается вопроса распространения MOC в View Controllers  пользовательского интерфейса, то предпочтительным является способ, когда приложение использует один и тот же контекст MOC, что существенно упрощает код и гарантирует корректность его исполнения.
Есть три способа добиться этого:

  1. Dependency Injection
  2. Singleton
  3. Использование UIApplication.sharedApplication ().delegate как разновидности Singleton

Некоторые могут рекомендовать вам использовать Singleton, но согласно рекомендациям Apple его стоит избегать.Dependency Injection описывается как возможность получить MOC из экземпляра NSManagedObject. Это очень полезно и правильно. В вашем AppDelegate вы устанавливаете контекст MOC для rootViewController, ассоциируемому с UIWindow. Затем ваш rootViewController может передавать тот же самый контекст MOC следующему View Controller напрямую или через экземпляр объекта NSManagedObject и далее по цепочке. Возьмем в качестве примера View Controller, который  имеет возможность добавлять и редактировать объект. Если мы передаем этому View Controller просто объект NSManagedObject, то получающий View Controller знает, что ему разрешено редактировать полученный NSManagedObject. Он не беспокоится о том, с каким MOC он работает. Это может быть main контекст, это может быть child контекст, он может находиться в совершенной изоляции в unit test, ему не надо об этом знать и беспокоиться.

Во всех наших экспериментальных приложениях мы будем использовать Dependency Injection.

Итак, план этой статьи следующий:

  • Модель Данных для экспериментальной задачи
  • Классический Core Data Stack на основе одного контекста MOC (как в шаблонах Apple) — приложение CoreData1Swift (Github)
  • MOC из UIManagedDocument — приложение CoreData2Swift (Github)
  • Core Data Stack на основе 2-х контекстов MOC, связанных отношением Parent-Child (Marcus Zara) — приложение CoreData3Swift (Github)
  • Использование Dependency Injection как методики распространения MOC в View Controllers пользовательского интерфейса — приложение CoreData4Swift (Github)

В конце статьи для общей картины мы представим приложение CoreData5Swift (Github)  с Singleton, чтобы понимать, что это такое.

В iOS 9 Apple предоставила в Core Data возможность ограничений на уникальность (Unique Constraints) атрибутов сущностей. Согласно этому вы можете задекларировать атрибут как уникальный для всех экземпляров определенной сущности. Эта возможность очень кратко демонстрировалась на WWDC 2015 (Session 220). Но когда пытаешься ее реализовать, то появляется ряд «подводных камней», особенно четко проявляющихся при импорте внешних данных, которые должны «сливаться» с уже существующими объектами базы данных. В этой статье мы также посмотрим, как работает этот механизм для различных вариантов MOC.
В качестве основы экспериментальных приложений для исследования Core Data будем использовать упрощенные варианты демонстрационных примеров из предыдущего стэнфордского курса CS193P » Developing iOS  7 Apps for iPhone and iPad» 2014 г. , переписанные на Swift.
Один такой вариант очень простого экспериментального приложения AdaptiveSplitViewController1Swift (Github), работающего с фотографиями, находящимися на сервере Flickr, который является облачным сервисом для хранения фотографий, представлен в статье Адаптивные Split View Controller и Popover на Swift в iOS 9.
Приложение AdaptiveSplitViewController1Swift запрашивает сервер Flickr о наиболее “свежих” по времени фотографиях и показывает их список  в таблице. Если вы кликните на любой фотографии в списке, то получите полномасштабное изображение этой фотографии:

Screen Shot 2016-03-16 at 10.32.04 AM

Это приложение каждый раз загружает с сервера заново 250 фотографий. Нам хотелось бы их запоминать, а подгружать только новые, но из-за большого количества данных мы не можем запоминать их в NSUserDefault. Нам нужна реальная база данных для постоянного хранения считанной с сервера информации и для оперативного показа этих данные в таблицах, а также для  использовании их в сложных запросах о Flickr фотографиях.
В этом посте мы модифицируем наше базовое экспериментальное приложение AdaptiveSplitViewController1Swift (Github) в направлении сохранения данных, полученных с сервера  Flickr, в Core Data. Для отображения данных из Core Data в таблицах будем использовать класс NSFetchedResultsController, но не напрямую, а через фантастически легкий в применении класс CoreDataTableViewSource, первоначально представленный в стэнфордском курсе CS193P » Developing iOS  7 Apps for iPhone and iPad» 2013 — 2014гг  и в дальнейшем реализованный на Swift в этом документе.
Подробное описание процесса создания приложения AdaptiveSplitViewController1Swift (Github), на основе которого мы будем создавать все экспериментальные приложения для этой статьи, можно посмотреть в  Адаптивные Split View Controller и Popover на Swift в iOS 9  в разделе “Экспериментальное приложение”. Но для того, чтобы вы поняли, какие изменения следует внесить в связи с появление в приложении Core Data, мне придется более подробно остановиться на архитектуре этого очень простого приложения AdaptiveSplitViewController1Swift (Github).
Пользовательский интерфейс этого приложения представляет собой адаптивный Split View Controller, где в качестве Master используется экранный фрагмент Flickr Photos для представления списка фотографий, а в качестве Detail — экранный фрагмент Image View Controller для показа изображения фотографии:

Screen Shot 2016-04-06 at 3.28.49 PM

Оба этих фрагмента обрамлены Navigation Controller.
В приложении внутренним представлением  фотографии, считанной с сервера Flickr, является структура Photo:

Screen Shot 2016-04-06 at 3.31.41 PM

Структура Photo содержит заголовок фотографии title, более детальную информацию о фотографии subtitle, уникальный идентификатор фотографии unique, URL изображения imageURL, сделавшего фотографию фотографа photographer
Структура Photo имеет convenience инициализатор init?(json:[String:AnyObject]), у которого на входе JSON данныe, полученные с сервера Flickr.com в виде словаря [String:AnyObject]

Screen Shot 2016-04-06 at 3.35.43 PM

Есть специальные требования формирования атрибутов Photo. Если у фотографии нет заголовка, то нужно использовать детальное описание subtitle фотографии как title. Если у фотографии нет ни заголовка title, ни детального описания subtitle,  нужно использовать “Unknown” как title. Ключи FLICKR_PHOTO_TITLE, FLICKR_PHOTO_ID, FLICKR_PHOTO_DESCRIPTION и словаря с информацией о Flickr фотографии определены в файле FlickrFetcher.h, содержащем public API для Flickr.com (об этом можно узнать здесь).
Массив [Photo] является Моделью для класса FlickrPhotosTVC, обслуживающего экранный фрагмент со списком фотогафий. Он наследует от UITableViewController и его задача заключается в показе списка фотографий, представленных массивом структур Photo:
FlickrPhotosTVC.swift

Screen Shot 2016-04-06 at 3.39.35 PM

Как обычно, этот класс использует массив [Photo] в методах UITableViewDataSource:

Screen Shot 2016-04-06 at 3.50.32 PM

а также при “переезде” на экранный фрагмент Image View Controller для показа изображения фотографии, который обслуживается классом ImageViewController. Моделью этого класса является свойство var imageURL: NSURL?, задающее URL изображения фотографии.
Cогласно концепции объектно-ориентированного программирования мы сделали класс FlickrPhotosTVC обощенным (generic), то есть он будет формировать таблицу фотографий, представленных массивом [Photo] и при выборе определенной строки в таблице показывать изображение соответствующей фотографии с помощью MVC ImageViewController. Но он не будет заботится о том, как получен массив [Photo] : c сервера Flickr или из хранилища NSUserDefaults.
Поэтому для показа фотографий  c сервера Flickr создан класс JustPostedFlickrPhotosTVC, который является subclass класса FlickrPhotosTVC:

JustPostedFlickrPhotosTVC.swift

Screen Shot 2016-04-06 at 3.53.51 PM

Данные c сервера Flickr будут загружаться в классе JustPostedFlickrPhotosTVC c использованием public API для Flickr, который был предоставлен стэнфордским сайтом.
Класс JustPostedFlickrPhotosTVC специально предназначен для считывания наиболее “свежих” фотографии с Flickr. Для этого используем public API для Flickr, который был предоставлен стэнфордским сайтом . Мы формируем URL о “недавних” фотографиях:

         let url = FlickrFetcher.URLforRecentGeoreferencedPhotos()

Считывание данных по этому URL происходит асинхронно и преобразуется полученные JSON данные в массив [Photo] одной строкой благодаря функции flatMap, выводу типа из контекста и Optional инициализатору Photo:
JustPostedFlickrPhotosTVC.swift

Screen Shot 2016-04-06 at 3.57.11 PM

Полученный в результате  парсинга массив self.photos “подхватывается” superclass FlickrPhotosTVC и мы видим таблицу фотографий при условии, что выставили на storyboard пользовательский класс JustPostedFlickrPhotosTVC для экранного фрагмента Flickr Photos.

Для того, чтобы показать пользователю, что идет загрузка данных из сети, которая требует некоторого времени для экранного фрагмента Flickr Photos включен на storyboard Refresh Control. Именно поэтому перед методом fetchPhotos () загрузки данных с сервера Flickr поставлен @IBAction — то есть это Action, “подвязанный” к Refresh Control.
Это все, что касается исходного экспериментального приложения.
НАМ ПРЕДСТОИТ заменить массив структур [Photo] на данные Core Data и обеспечить их считывание в родительском классе FlickrPhotosTVC.

Первым шагом будет копирование и переименование приложения AdaptiveSplitViewController1Swift ( Github) в CoreData1Swift. Надо отметить, что переименование проекта в Xcode 7 не является тривиальной задачей.
Вторым шагом мы создадим Модель Данных для нашей базы данных.

Модель Данных

Идем в меню File -> New -> File -> и выбираем разделы iOS и Core  Data, а в нем файл Data Model и даем имя нашей Модели Данных “CoreData1Swift” :

Screen Shot 2016-04-06 at 4.01.16 PM

В результате в нашем проекте появляется файл CoreData1Swift.xcdatamodeld , с которым мы можем работать в  специальном редакторе для Моделей Данных. Там мы можем задавать описание хранимых объектов в виде сущностей (Entity)  и их атрибутов (Attribute). Но Core Data идет дальше, она сохраняет не только информацию о сущностях, но и об их взаимосвязях, об ограничениях, накладываемых на атрибуты сущностей, например, ввиде диапазона значений, значения по умолчанию или уникальности и т.д.

Screen Shot 2016-04-06 at 4.41.56 PM

Для нашей задачи нам понадобятся две сущности: Photo и Photographer (вы их видите на рисунке сверху и снизу). Атрибуты сущностей Photo и Photographer соответствуют тем, которые были у нас в структуре Photo, за исключением атрибута imageURLS, который мы используем для хранения URL как String и который в дальнейшем будет преобразован в атрибутimageURL типа NSURL.

Screen Shot 2016-04-06 at 4.44.52 PM

На рисунке также представлены возможные типы хранимых данных, хотя теоретически вы можете хранить любые данные, и ниже мы покажем, как это сделать.
Модель Данных Core Data можно представить графически c переключателя Editor Style в правом нижнем углу

Screen Shot 2016-04-06 at 4.46.24 PM

В графическом представлении удобно создавать взаимосвязи сущностей. Для этого выполним CTRL-перетягивание от одного объекта к другому. В результате на обоих сущностях создается Взаимосвязь со стандартным именем newRelationship с обоих сторон.

Screen Shot 2016-04-06 at 4.48.38 PM

У нас Взаимосвязь будет иметь разные имена и свойства на каждой из сторон.
Со стороны Photo мы назовем Взаимосвязь whoTook, что для Photo означает “кто сделал фотографию”. Тип этой связи будет “один-к-одному” (“To One”).

Screen Shot 2016-04-06 at 4.50.31 PM

Со стороны Photographer мы назовем Взаимосвязь photos, потому что фотограф может сделать множество фотографий.  Тип этой связи будет “один-ко-многим” (“To Many”). Двойная стрелочка как раз показывает тип связи “один-ко-многим”.

Screen Shot 2016-04-06 at 4.52.28 PM

В Photo Взаимосвязь с именем whoTook можно посмотреть и в табличном режиме:

Screen Shot 2016-04-06 at 4.54.09 PM

В Photographer  Взаимосвязь с именем photos также можно посмотреть и в табличном режиме:

Screen Shot 2016-04-06 at 4.55.45 PM

Добавляем ограничения на атрибуты сущности Photo — атрибут unique должен быть уникальным:

Screen Shot 2016-04-06 at 4.57.31 PM

Добавляем ограничения на атрибуты сущности Photographer — атрибут name должен быть уникальным:

Screen Shot 2016-04-06 at 4.59.05 PM

Для того, чтобы работать со Swift объектами Core Data в коде, мы должны создать для них пользовательские классы с помощью меню Editor -> Create NSManagedObject subclass

Screen Shot 2016-04-06 at 5.01.02 PM

Далее задаем Модель Данных, для которой мы хотим создать пользовательские классы, так как в проекте их может быть несколько:

Screen Shot 2016-04-06 at 5.02.45 PM

Задаем сущности:

Screen Shot 2016-04-06 at 5.04.10 PM

И получаем 4 файла — по два файла на каждую сущность:
Photo.swift
Photo+CoreDataProperties.swift
Photographer.swift
Photographer+CoreDataProperties.swift

Screen Shot 2016-04-06 at 5.06.37 PM

Если вы исследуете эти файлы, например, для сущности Photo, то увидите, что файл Photo.swift  практически пустой — он только определяет subclass NSManagedObject:

Screen Shot 2016-04-06 at 5.49.58 PM

, а файл Photo+CoreDataProperties.swift выглядит следующим образом:

Screen Shot 2016-04-06 at 5.52.03 PM

Во-первых, в файле Photo+CoreDataProperties.swift присутствует ключевое слово @NSManaged, которое говорит о том, что Core Data сама добавит getters и setters этому свойству при запуске приложения и сама будет отслеживать все его изменения. Во-вторых, заметьте, что используется extension Photo, а не class Photo, что говорит о том, что Xcode стал умнее: Photo.swift — это пустой класс, который вы можете наполнить своей собственной функциональностью, а Photo+CoreDataProperties.swift — это расширение (extension) этого класса, куда Core Data пишет свои свойства. Это означает, что если вы когда-нибудь добавите атрибуты к сущности Photo, и заново регенерируете NSManagedObject subclass, то Xcode перезапишет только Photo+CoreDataProperties.swift, оставляя ваши собственные изменения в Photo.swift  нетронутыми.
Давайте разместим convenience инициализатор объекта Photo по данным, считанным с сервера  Flickr с уже знакомым нам кодом из статьи Адаптивные Split View Controller и Popover на Swift в iOS 9 :
Photo.swift

Screen Shot 2016-04-06 at 5.55.41 PM

Согласно правилам работы с инициализаторами в Swift convenience  инициализатор всегда должен вызывать прямым или косвенным образом designated (назначенный) инициализатор, которым для объектов Core Data является

init(entity: NSEntityDescription, insertIntoManagedObjectContext context:   NSManagedObjectContext?)

и который не рекомендуется переопределять (override).
Файл Photo+CoreDataProperties.swift определяет атрибуты сущности Photo в виде свойств:

Screen Shot 2016-04-06 at 6.16.47 PM

Здесь представлено расширение класса Photo, который является subclass NSManagedObject. Видно, что хранение URL для изображения фотографии организовано как private  var imageURLS: String, а пользователю дается доступ к public вычисляемому свойству var imageURL:  NSURL?. Таким образом (через вычисляемое свойство) можно организовать хранение атрибутов любого типа. Взаимосвязь с сущностью Photographer, представлена свойством var whoTook: Photographer?
Для сущности Photographer в файле Photographer.swift мы также можем разместить convenience  инициализатор Photographer по имени name:
Photographer.swift

Screen Shot 2016-04-06 at 6.19.56 PM

Для сущности Photographer, файл Photographer+CoreDataProperties.swift будет иметь следующий вид:
Screen Shot 2016-04-06 at 6.22.04 PM

Взаимосвязь с сущностью Photo — фотографии, которые сделал данный фотограф. Множеством NSSet отображает связь “один-ко-многим” в пользовательских классах NSManagedObject.
Итак, у нас есть наши Swift объекты Photo и Photographer, которые одновременно являются и NSManagedObjects. Как упоминалось выше, все эти объекты “живут и работают” на “центральном пространстве”, называемом контекст NSManagedObjectContext (MOC). И именно MOC отображает наши NSManagedObjects в объекты базы данных SQLite.
Инициализация MOC проводится с помощью либо Core Data Stack, либо UIManagedDocument. Начнем с первого.

Классический Core Data Stack

Код для Core Data Stack, генерируемый Apple при указании опции “Use Core Data” в шаблоне проекта, располагается в AppDelegate, но ему там не место — он должен иметь отдельный класс CoreDataStack, задача которого состоит в предоставлении контекста mainMoc:
CoreDataStack.swift

Screen Shot 2016-04-06 at 6.27.15 PM

Инициализация контекста mainMoc — экземпляра класса UIManagedObjectContext, выполняется с помощью назначенного инициализатора (Designated Initializer)
NSManagedObjectContext (concurrencyType: .MainQueueConcurrencyType), в котором нужно указываем тип “параллельной” очереди для функционирования контекста. В нашем случае это main queue.
Кроме того, для контекста mainMoc можно указать координатор persistentStoreСoordinator, который “завязывает” на себя Модель Данных и обеспечивает постоянное хранение в определенном формате (в нашем случае
SQLite):
CoreDataStack.swift

Screen Shot 2016-04-06 at 6.31.36 PM

Инициализация координатора сoordinator — экземпляра  класса NSPersistentStoreCoordinator, выполняется назначенным инициализатором (Designated Initializer) на основе Модели Данных:
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.model)
Затем к координатору добавляется хранилище для постоянного хранения с определенным URL persistentStoreURL.
И наконец,Модель Данных определяется файлом с определенным именем и расширением “.momd” в рабочей директории приложения:
CoreDataStack.swift

Screen Shot 2016-04-06 at 6.36.10 PM

Таким образом, мы прошлись вниз по иерархической структуре Core Data Stack от контекста MOC до координатора с хранилищем и Моделью Данных:

Screen Shot 2016-04-06 at 6.38.10 PM

Мы завершили создание всех свойств специального класса CoreDataStack (файл CoreDataStack.swift) для классического варианта с одним MOC на main queue. В этом классе у нас 3 основных компонента Core Data Stack:

  1. lazy var mainMoc: NSManagedObjectContext  — контекст
  2. private lazy var coordinator: NSPersistentStoreCoordinatorкоординатор вместе с хранилищем persistentStoreURL
  3. private lazy var model: NSManagedObjectModel Модель Данных

Добавляем еще в этот класс метод сохранения контекста saveMainContext():
CoreDataStack.swift

Screen Shot 2016-04-06 at 6.40.58 PM

Класс CoreDataStack — это единственное место, где вы будете иметь дело с private свойствами coordinator и model. Единственное public свойство в классе CoreDataStack — это mainMoc и его мы будем использовать для работы в Core Data.

Отображение данных Core Data в Table View Controller с помощью NSFetchResultController

Core Data — это большой граф объектов, а Table View — реально хороший инструмент для обхода графа объектов. Как нам добиться их взаимодействия? В iOS есть замечательный класс  с именем NSFetchedResultsController.

Единственной целью этого класса является связывание запроса fetchRequest с Table View.
Все, что выбирается из Core Data с помощью запроса fetchRequest, показывается в Table View, даже если база данных меняется во время запроса fetchRequest. В этом случае Table View обновляется. Способ, каким все это работает, включает в себя две части.
Первая часть связана с ответами на вопросы протокола UITableViewDataSource: ”Сколько секций? Сколько строк в секциях?” и некоторые другие. Экземпляр класса NSFetchedResultsController в состоянии ответить на все эти вопросы.
Он также может сообщить вам в любое время, какие сущности находятся в вашей базе данных, какая сущность показывается в данной строке.
Существует соответствие между строкой в таблице и некоторым объектом в базе данных. Мы знаем, что когда мы создаем fetchRequest, то он возвращает массив объектов определенного вида. И у нас есть возможность их получить с помощью метода objectAtIndexPath. Вы посылаете этот метод fetchedResultsController и он может вернуть вам Photo или Photographer или какой-то NSManagedObject, который является объектом, сответствующим этой строке. Затем вы “вытаскиваете” его атрибуты и размещаете их в вашей ячейке UITableViewСell, соответствующей строке с indexPath.
Вторая часть NSFetchedResultsController — это делегат NSFetchedResultsControllerDelegate, используя который вы можете отслеживать все изменения в Core Data, и если что-то меняется, то воздействовать на fetchRequest, чтобы он изменил вашу таблицу, что совершенно замечательно. Это означает, что если вы добавили фотографию, которая подходит под ваш запрос fetchRequest, то это добавит строку в вашу таблицу, и вам не надо ничего делать, так как это отслеживается автоматически. NSFetchedResultsController “слушает” все изменения, которые происходят в MOC.
Как мы создаем NSFetchedResultsController?
Нам необходим fetchRequest и контекст MOC, в котором мы собираемся производить выборку. NSFetchedResultsController даже создает заголовки для секций, так что вы можете определить, какие атрибуты объектов дадут имя секции. Согласно им он делит таблицу на секции. Он также выполняет кэширование. О последних двух параметрах чуть позже.
Давайте взглянем на fetchRequest, который мы можем создать для FetchedResultsController :

Screen Shot 2016-04-06 at 8.24.00 PM

Я создаю Photo запрос, то есть я буду выбирать фотографии. Мне необходим sortDescriptor, который будет определять порядок, в котором фотографии будут появляться в Table View, допустим, что я сортирую их по “title” (заголовку).
Затем предикат, predicate. Я создаю предикат для выборки всех фото, сделанных фотографом с заданным именем self.photographer. Затем я создаю нормальный NSFetchedResultsController с помощью инициализатора c fetchRequest, managedObjectContext, sectionNameKeyPath и cacheName. Это просто. Относительно последних двух параметров. Один из них cacheName, который вы можете задать равным nil, то есть кэширования не будет. Кэширование означает, что кэшируются результаты запроса между запусками вашего приложения. Другими словами кэш будет сохранятся постоянно, на диске.
Это не кэш в памяти. Вы знаете, что Core Data постоянно кэширует данные в памяти. Но здесь кэширование сохраняется между запусками приложения. Если вы сделаете кэш не nil, то вы должны использовать в точности один и тот же запрос при каждом запуске приложения. Если что-то меняется в вашем  fetchRequest, то возвращаясь к NSFetchedResultsController и пытаясь использовать кэш, вы получите аварийное завершение приложения. Реально кэш предназначен только для Table Views, имеющих всегда один и тот же запрос fetchRequest: те же самые предикаты и дескрипторы сортировки.
Если вы собираетесь использовать sectionNameKeyPath, создавая секции в своей таблице, то sortDescriptors должны соответствовать sectionNameKeyPath. Другими словами, строки в таблице, которые вы выбираете, должны располагаться точно в том порядке, который соответствует заголовкам секций. Поэтому почти всегда ваш первый sortDescriptor будет заголовком для ключей секций.
Таким образом, существуют две вещи, которые “подвязывают” FetchedResultsController к вашей таблице. Одна — это реализация всех методов UITableViewDataSource, и вторая — реализация методов делегата NSFetchedResultsControllerDelegate для “отслеживания” изменений в Core Data. Для этого вам нужно скопировать код  из документации для NSFetchedResultsController и вставить в ваш subclass класса NSFetchedResultsController, что очень обременительно.Но Стэнфордский университет сделал все необходимые компановки и вставки кода и предоставил нам фантастический класс с именем CoreDataTableViewController. Он наследует от UITableViewController и fetchedResultsController добавлен в него как свойство:
CoreDataTableViewController.swift

Screen Shot 2016-04-06 at 8.28.59 PM

Как только вы его установите, начнется использование этого свойства для заполнения таблицы UITableView. Свойство fetchedResultsController — это единственное свойство класса CoreDataTableViewController, с которым мы будем иметь дело. Каким бы громоздким не был код  этого класса, мы можем о нем забыть и помнить только то, что у него есть свойство fetchedResultsController, которое мы должны конфигурировать соответствующим образом и у которого есть метод objectAtIndexPath для получения любого объекта в любой строке и секции таблицы.
Давайте разместим CoreDataTableViewController в нашем проекте:

Screen Shot 2016-04-06 at 8.32.56 PM

Как и в базовом приложении AdaptiveSplitViewController1Swift  (Github), мы создадим в нашем приложении обобщенный класс PhotosCDTVC, который наследует от CoreDataTableViewController и отображает выбранные объекты Photo независимо от того, каков критерий их выборки.

Выше говорилось, что CoreDataTableViewController реализует методы UITableViewDataSource, но не может реализовать основной метод — это cellForRowAtIndexPath. Действительно, fetchedResultsController известны атрибуты title, subtitle сущности Photo, но он не знает, как их разместить в табличной ячейке UITableViewСell.
Так что нам придется самим реализовать этот метод. Этот метод выглядит обычно, за исключением того, что объект photo в нашем случае мы должны получить из свойства self.fetchedResultsController с помощью уже упомянутого метода  objectAtIndexPath:
PhotosCDTVC.swift

Screen Shot 2016-04-06 at 8.36.10 PM

В этом же классе мы можем реализовать метод prepareForSegue для “переезда” на экранный фрагмент Image View Controller для показа полномасштабного изображения для выбранной из списка фотографии, и опять работает метод objectAtIndexPath:
PhotosCDTVC.swift

Screen Shot 2016-04-06 at 8.40.22 PM

Для “подкачки” фотографий  c сервера Flickr , загрузки их в Core Data мы имеем в приложении другой класс JustPostedFlickrPhotosTVC, который является subclass класса PhotosCDTVC:
JustPostedFlickrPhotosTVC.swift

Screen Shot 2016-04-06 at 8.42.44 PM

Для работы с Core Data нам нужен экземпляр стэка coreDataStack. Как только мы его установим (об этом немного позже), срабатывает Наблюдатель свойства didSet{}, в котором определяется контекст self.moc и вызывается метод fetchPhotos выборки данных с Flickr  сервера и записи их в Core Data:
JustPostedFlickrPhotosTVC.swift

Screen Shot 2016-04-06 at 8.45.24 PM

Конструкция
_ = flickrPhotos.flatMap({ (json) -> Photo? in
return Photo.init(json: json, context: context) })

очень похожа на аналогичные конструкции экспериментальных приложений в статье Адаптивные Split View Controller и Popover на Swift в iOS 9, когда происходила запись данных с Flickr  сервера в массив структур [Photo]
             self.photos = flickrPhotos.flatMap(Photo.init)
Та же функция flatMap, json — на входе, но в нашем приложении замыкание сложнее из-за дополнительного параметра — контекста context.
Экранный фрагмент Flickr Photos, который обслуживается классом JustPostedFlickrPhotosTVC, — это топовый View Controller

Screen Shot 2016-04-06 at 10.01.41 PM

и нам неоткуда получить стэк coreDataStack как из AppDelegate. Поэтому устанавливаем его там:
AppDelegate.swift

Screen Shot 2016-04-06 at 10.03.37 PM

В классе JustPostedFlickrPhotosTVC необходимо конфигурировать свойство fetchedResultsController для родительского класса PhotosCDTVC — единственное public свойство класса CoreDataTableViewController, с которым мы работаем:
JustPostedFlickrPhotosTVC.swift

Screen Shot 2016-04-06 at 10.06.53 PM

Screen Shot 2016-04-06 at 10.08.21 PM

Выбираем все фотографии, поэтому предикат predicate отсутствует. Сортировка выполняется по заголовку фотографии title в порядке возрастания. Секций нет, кэширование между запусками приложения не производится.

Каждый раз при запуске приложения CoreData1Swift происходит загрузка новых наиболее свежих данных о фотографиях с Flickr сервера и, естественно, в Core Data уже могут находиться фотографии, которые мы загружаем. Для того, чтобы одни и те же фотографии не загружались многократно, мы указали в Модели Данных, что атрибут фотографии unique является уникальным:

Screen Shot 2016-04-06 at 10.11.04 PM

Но этого недостаточно. Дело в том, что ограничения на уникальность (Unique Constraints) не предотвращают создание дубликатов в контексте MOC. Core Data проверяет уникальность согласно выставленным ограничениям только когда вы пытаетесь сохранить контекст MOC. Если Core Data обнаружила более одного объекта с тем же самым значением, для которого выставлена уникальность (Unique Constraints), то по умолчанию она прерывает приложение и сообщает об ошибке error. Это происходит из-за того, что по умолчанию значением “политики разрешения конфликтов при слиянии” (merge policies) является NSErrorMergePolicyType. При других значениях “политики” обрабатывается информация о  конфликтующих объектах, содержащаяся в элементе словаря error.userInfo с ключем conflictList. Конфликтующие объекты — это объекты находящиеся в контексте MOC (в “памяти”) и объекты, записанные в SQLite (условно на диске), и имеющие одно и то же значение атрибута, относительно которого выставлены ограничения на уникальность. Назовем условно объекты, находящиеся MOC (в “памяти”), “новыми”, а объекты, находящиеся в SQLite (на диске), “старыми”.
Если вы хотите, чтобы при возникновении конфликтов ваше приложение не прерывалось, а конфликты разрешались автоматически, то вы должны задать другую политику merge policies вместо той, которую Core Data предлагает вам по умолчанию.

У вас есть два выбора:

  1. NSMergeByPropertyObjectTrumpMergePolicy “новые” данные (MOC) имеют преимущество перед “старыми”
  2. NSMergeByPropertyObjectTrumpMergePolicy “старые” данные (SQLite) имеют преимущество перед “новыми”

Выбор политики зависит от задачи. В обычном сценарии при импорте данных из внешних источников выбирают  NSMergeByPropertyObjectTrumpMergePolicy. Позже мы увидим, что это довольно затратная по времени стратегия для нашей задачи, и, возможно, мы выберем противоположную стратегию, но пока в классе CoreDataStack при создании контекста MOC мы используем общепринятую  “политику” — NSMergeByPropertyObjectTrumpMergePolicy:
CoreDataStack.swift

Screen Shot 2016-04-06 at 10.15.01 PM

В нашем приложении новые данные с сервера Flickr мы размещаем в контексте context с помощью следующего кода:
JustPostedFlickrPhotosTVC

Screen Shot 2016-04-06 at 10.16.58 PM

Дубликаты (если они есть) остаются в контексте context до тех пор, пока не закончится операция сохранения:

Screen Shot 2016-04-06 at 10.18.38 PM

Иногда сохранение выполняется асинхронно в фоновой очереди (background queue), например, если используется UIManagedDocument. Тогда вы можете наблюдать дубликаты на экране до тех пор, пока не сработает автосохранение (об этом позже).
При первом запуске приложения CoreData1Swift мы видим, что вначале работает Refresh Control, затем таблица заполняется и мы можем выбрать фотографию в списке и посмотреть ее изображение:

Screen Shot 2016-04-06 at 10.20.47 PM

На консоле выводится имя файла, в котором располагается наша SQLite:

Screen Shot 2016-04-06 at 10.22.43 PM

Идем туда и смотрим схему полученной базы данных CoreData1Swift.sqlite:

Screen Shot 2016-04-06 at 10.24.15 PM

Мы видим, что ограничения на уникальность атрибута unique сущности Photo и атрибута name сущности Photographer “правильно” отобразились в базе данных CoreData1Swift.sqlite как уникальные. Иногда, если вы, например, забываете сохранить файл CoreData1Swift.xcdatamodeld с Моделью Данных, то у вас могут возникнуть проблемы с уникальностью. В этом случае смотрите схему полученной базы данных SQLite. Она должна выглядеть именно так.
Теперь давайте организуем для пользователя возможность удаления фотографии. Для этого добавим в класс PhotoCDTVC  соответствующий метод делегата UITableViewControllerDelegate:

Screen Shot 2016-04-06 at 10.26.44 PM

Здесь показано как можно взять context у объекта Photo и проводить с ним все операции, включая удаление объектов. Кроме удаление Core Data объекта не требует удаления в таблице соответствующей строки, так как CoreDataTableViewController, от которого мы наследуем, сама будет отслеживать удаление объектов Core Data и удалять соответствующие строки в таблице.
Удаляя photo, мы хотим удалить соответствующего фотографа, если у него после удаления фотографии не осталось никаких фотографий вообще. Для этого используем подготовку к удалению сущности Photo:
Photo.swift

Screen Shot 2016-04-06 at 10.28.42 PM

Запускаем приложение и удаляем одну из фотографий:
До удаления

Screen Shot 2016-04-06 at 10.30.15 PM

После Удаления

Screen Shot 2016-04-06 at 10.31.54 PM

Код экспериментального приложения с классическим Core Data Stack находится в приложении CoreData1Swift (Github).
Продолжение находится здесь.

Core Data в iOS 9 и Swift при ограничениях на уникальность. Часть 1.: 2 комментария

  1. «У вас есть два выбора:
    NSMergeByPropertyObjectTrumpMergePolicy — “новые” данные (MOC ) имеют преимущество перед “старыми”
    NSMergeByPropertyObjectTrumpMergePolicy — “старые” данные (SQLite) имеют преимущество перед “новыми”»

    один из них должен быть ByPropertyStore кмк.

    А можете рассказать чуть подробнее почему вместо того что бы сетить свойство контекст контроллера из делегата я не могу просто взять его из делегата когда мне удобно?

    • Спасибо за обнаружение ошибки в тексте. Исправлено.
      Можно взять и напрямую из AppDelegate:
      var managedObjectContext: NSManagedObjectContext? =
      (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext

      Но установку через Selector(«setCoreDataStack:») я подсмотрела на сайте raywenderlich.com в прекрасном цикле Видео-уроков «Intermediate Core Data», навеялись воспоминания Objective-C, и показалось забавно использовать сигнатуру Objective-C метода в Swift.
      Кстати, если вы читаете эту статью, то часть, связанная с UIManagedDocument кардинально изменена — смотрите код, а не текст, пока до текста не добралась.
      А еще лучше Задание 5 для iOS 9, решение которого опубликую через 4-5 дней.

Обсуждение закрыто.