Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Core Data
Code

Core Data и Swift: Управляемые объекты и fetch-запросы

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Core Data and Swift.
Core Data and Swift: Data Model
Core Data and Swift: Relationships and More Fetching

Russian (Pусский) translation by Antony Karpov (you can also view the original English article)

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

Вы также узнаете несколько других классов Core Data, таких как NSFetchRequest и NSEntityDescription. Позвольте мне начать с введения о NSManagedObject, вашего нового лучшего друга.

Необходимые условия

То, что я расскажу в этой серии про Core Data применимо к iOS 7+ и OS X 10.10+, но основное внимание будет уделяться iOS. В этой серии статей я буду работать с Xcode 7.1 и Swift 2.1. Если вы предпочитаете Objective-C, то я рекомендую прочитать мои предыдущие статьи о Core Data.

1. Управляемые объекты

Экземпляр NSManagedObject представляет собой запись резервного хранилища Core Data. Запомните, не имеет значения, как выглядит это резервное хранилище. Однако, возвращаясь к аналогии с базой данных, экземпляр NSManagedObject содержит информацию строки в таблице базы данных.

Причина, по которой Core Data использует NSManagedObject вместо NSObject, как свой базовый класс для моделирования записей, будет понятна немного позже. Прежде чем мы начнем работать с NSManagedObject, нам необходимо знать несколько вещей об этом классе.

NSEntityDescription

Каждый экземпляр NSManagedObject связан с экземпляром NSEntityDescription. Описание сущности включает в себя сведения об управляемых объектах, так сущность управляемых объектов включает в себя также его атрибуты и отношения.

NSManagedObjectContext

Управляемый объект также связан с экземпляром NSManagedObjectContext. Контекст управляемого объекта, к которому относится управляемый объект, следит за изменениями этого управляемого объекта.

2. Создание записи

С учетом вышесказанного, создание управляемого объекта делается довольно незамысловато. Чтобы убедиться в том, что управляемый объект настроен должным образом, рекомендуется использовать специальный инициализатор для создания новых экземпляров NSManagedObject. Давайте посмотрим, как происходит создание нового объекта person.

Откройте проект из предыдущей статьи или клонируйте репозиторий с GitHub. Так как мы не делаем полнофункционального приложения в этой статье, будем делать большую часть нашей работы в классе делегата приложения, AppDelegate. Откройте AppDelegate.swift и обновите реализацию application(_:didFinishLaunchingWithOptions:), как показано ниже.

Первое, что мы делаем, - создаем экземпляр класса NSEntityDescription путем вызова entityForName(_:inManagedObjectContext:). Мы передаем имя сущности для которой хотим создать управляемый объект - "Person" и экземпляр NSManagedObjectContext.

Почему нам нужно передавать еще и объект NSManagedObjectContext? Мы указываем имя для управляемого объекта, который мы хотим создать, но мы также должны сказать Core Data, где можно найти модель данных для этой сущности. Помните, что контекст управляемого объекта прочно связан с координатором постоянного хранилища, который хранит ссылку на модель данных. Когда мы передаем контекст управляемого объекта, Core Data запрашивает у координатора постоянного хранилища модель данных, чтобы найти сущность, которая нам нужна.

На втором шаге мы вызываем назначенный инициализатор класса NSManagedObject, init(entity:insertIntoManagedObjectContext:). Мы передаем описание сущности и экземпляр NSManagedObjectContext. Что? Почему мы должны снова передавать экземпляр NSManagedObjectContext? Вспомните, что я писал ранее. Управляемый объект связан с описанием сущности и она живет в контексте управляемого объекта, именно поэтому мы сообщаем Core Data с каким контекстом управляемого объекта должны быть связан новый управляемый объект.

Это не слишком сложно. Да? Теперь мы создали новый объект person. Как нам изменить его атрибуты или установить взаимосвязь? Это делается с помощью метода ключ-значение (key-value). Чтобы изменить имя [first] нового объекта person, который мы только что создали, мы сделаем следующее:

Если вы знакомы с методом ключ-значение, то это должно выглядеть очень похоже. Поскольку класс NSManagedObject соответствует протоколу NSKeyValueCoding, мы устанавливаем атрибут, вызывая setValue(_:forKey:). Это так просто.

Одним из недостатков этого подхода является то, что вы легко можете ошибиться, написав неправильно имя атрибута или взаимосвязи. Кроме того, имена атрибутов Xcode не автодополняет, как, например, имена свойств. Эту проблему легко решить, мы поговорим об этом позже, в следующих статьях этой серии.

Прежде чем мы продолжим наше изучение NSManagedObject, давайте установить атрибуту age  объекта newPerson значение 44.

3. Сохранение записи

Несмотря на то, что у нас теперь есть новый экземпляр person, Core Data еще не сохранила этот объект в свое резервное хранилище. На данный момент управляемый объект, который мы создали, живет только в контексте управляемого объекта, куда он был добавлен. Чтобы сохранить объект person в резервное хранилище, нам нужно сохранить изменения контекста управляемого объекта путем вызова save() у него.

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

Постройте и запустите приложение, чтобы увидеть, что все работает так, как ожидалось. У вас тоже аварийное завершение? Что вам говорит консоль вывода? Это похоже на результат, что ниже?

Xcode говорит нам, что он ожидал экземпляр NSDate для атрибута first, но мы передали String. Если вы откроете модель Core Data, которую мы создали в предыдущей статье, то увидите, что типом атрибута first действительно является Date. Измените его на String и запустить приложение еще раз.

Снова аварийное завершение? Несмотря на то, что это более сложная тема, важно понять, что происходит.

Совместимость моделей данных

Выходные данные в консоли Xcode должен выглядеть аналогично тому, как представлено ниже. Обратите внимание, что ошибка отличается от предыдущей. Xcode говорит нам, что модель, которая используется для открытия хранилища не совместима с той, что использовалась для его создания. Как же это произошло?

Когда мы впервые запустили приложение, несколько минут назад, Core Data проверив модель данных, создал на ее основе для нас хранилище, - базу данных SQLite в данном случае. Core Data все же умен. Это дает гарантию того, что структура резервного хранилища и что модели данных совместимы. Это жизненно важно, убедиться, что мы получаем обратно из резервного хранилища то, что ожидаем, то, что отправили туда в первый раз.

Во время первой аварийного завершения мы заметили, что наша модель данных содержит ошибку, и мы изменили тип атрибута first с Date на String. Другими словами, мы изменили модель данных, несмотря на то, что Core Data уже создал хранилище для нас на основе неправильной модели данных.

После обновления модели данных, мы запустили приложение снова и получили второе аварийное завершение. Одна из вещей, которые Core Data делает при создании стека - удостовериться, что модель данных и резервное хранилище (если оно создано) совместимы. Чего и не было в нашем примере, поэтому приложение аварийно завершилось.

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

Так как у нас нет миллионов пользователей нашего приложения, мы может безопасно его удалить с тестового устройства и запустить приложение еще раз. Если все прошло хорошо, новый объект person теперь безопасно хранится в хранилище, базе данных SQLite, созданном Core Data для нас.

Проверка резервного хранилища

Вы можете проверить, что операция сохранения работает,  заглянув внутрь базы данных SQLite. Если вы запускали приложения в эмуляторе, то перейдите к /Users/<USER>/Library/Developer/CoreSimulator/Devices/<DEVICE_ID>/data/Containers/Data/Application/<APPLICATION_ID>/Documents/SingleViewCoreData.sqlite. Так как расположение данных приложения изменяется с каждым выпуском Xcode, указанный выше путь действителен только для Xcode 7.

Откройте базу данных SQLite и проверьте таблицу с именем ZPERSON. Таблица должна иметь одну запись, ту, который мы добавили минуту назад.

Contents of the SQLite Database

Вы должны иметь ввиду две вещи. Во-первых, нет необходимости понимать структуру базы данных. Core Data управляет резервным хранилищем за нас, и нам нет необходимости понимать его структуру для работы с Core Data. Во-вторых, никогда не используйте прямой доступ к резервному хранилищу. Core Data наполняет резервное хранилище, и мы должны уважать это, если мы хотим, чтобы Core Data делал свою работу хорошо. Если мы начнем взаимодействовать с базой данных SQLite — или любым другим типом хранилища — нет гарантии, что Core Data будет продолжать функционировать должным образом. Короче говоря, Core Data отвечает за хранилище, так что оставьте его в покое.

4. Извлечение записей

Несмотря на то, что мы будем подробно рассматривать  NSFetchRequest в следующей статье, нам нужен класс NSFetchRequest , чтобы запросить у Core Data информацию об объектном графе, которым он управляет. Давайте посмотрим, как мы можем получить запись, которую мы добавили ранее с помощью NSFetchRequest.

После инициализации fetch-запроса, мы создаем объект NSEntityDescription и присваеваем его свойству entity этот fetch-запрос. Как вы можете видеть, мы используем класс NSEntityDescription для того, чтобы сказать Core Data, какая сущность нам интересна.

Выборку данных выполняет класс NSManagedObjectContext. Мы вызываем executeFetchRequest(_:), передав туда наш fetch-запрос. Так как executeFetchRequest(_:) является throw-методом [то есть может привести к аварийному завершению работы приложения], мы "заворачиваем" его вызов в оператор do-catch.

Если fetch-запрос успешно выполнен, то метод вернет результирующий массив. Обратите внимание, что Core Data всегда возвращает массив при успешном выполнении запроса, даже если ожидаем только один результат или если Core Data не удалось найти каких-либо записей.

Запустите приложение и проверьте вывод консоли в Xcode. Ниже вы можете увидеть, что было возвращено в виде массива с одним объектом типа NSManagedObject. Сущность объекта - Person [человек].

Для доступа к атрибутам записи, мы используем метод ключ-значение (как делали ранее). Важно хорошо усвоить работу с методом ключ-значение, если вы планируете работать с Core Data.

Вы наверно удивитесь, почему я вывел объект person до и после вывода имени person. На самом деле это один из самых важных уроков этой статьи. Взгляните на вывод ниже.

В первый раз, когда мы выводим объект person в консоль, мы видим, data: <fault> [значение отсутствует] Второй раз, однако, данные уже содержат атрибуты и взаимосвязи объекта. Почему так? Это все связано с faulting, ключевой концепцией Core Data.

5. Faulting

Концепция, лежащая в основе faulting [наличие  отсутствующих  значений], не уникальна для Core Data. Если вы когда-нибудь работали с Active Record на Ruby on Rails, то следующее будет безусловно знакомо. Концепции не являются идентичными, но очень похожи с точки зрения разработчика.

Core Data старается удерживать в памяти настолько мало, насколько это возможно. Это одна из используемых стратегий faulting. Когда мы извлекли записи для сущности Person минуту назад, Core Data выполнил запрос и получение данных, но он не полностью инициализирует эти записи.

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

В подавляющем большинстве случаев об этом совершенно не нужно беспокоиться. В тот момент, когда вы захотите получить доступ к атрибуту или взаимосвязи управляемого объекта, fault становиться fired, что означает, что Core Data заменяет fault в управляемом объекте. Вы можете это наблюдать в нашем примере, также это объясняет, почему второй вывод объекта person не напечатал fault в консоле.

Faulting - это то, на чем спотыкаются многие новички и поэтому я хочу убедиться, что вы понимаете основы этой концепции. Мы узнаем больше о faulting в следующих статьях этой серии. Если вы хотите узнать больше о Core Data именно в части faults, то возможно вы захотите прочитать этот Глубокий взгляд на faulting в Core Data.

6. Обновление записей

Обновление записей так же просто, как и создание новой записи. Вы получаете запись, измените атрибут или взаимосвязь и сохранить контекст управляемого объекта. Так как управляемый объект, запись, связаны с контекстом управляемого объекта, последнему известно о любых изменениях, добавлении, обновлении и удалении. Когда контекст управляемого объекта сохраняется, Core Data передает все в резервное хранилище.

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

Вы можете убедиться в успешном обновлении [записи], взглянув на хранилище SQLite, как мы делали ранее.

Updating a Record in the Backing Store

7. Удаление записей

Удаление записи по той же схеме. О том, что запись надо удалить из постоянного хранилища мы сообщаем контексту управляемого объекта путем вызова deleteObject(_:) и передачи ему управляемого объекта, который необходимо удалить.

Удалите в нашем проекте объект person, который мы получили ранее, передав его в метод контекста управляемого объекта deleteObject(_:). Обратите внимание, что операция удаления не будет передана в резервное хранилище, до тех пор, пока мы вызовем save() у контексте управляемого объекта.

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

Deleting a Record from the Backing Store

Заключение

В этой статье мы рассмотрели намного больше, чем просто создание, получение, обновление и удаление записей. Мы затронули несколько важных концепций, лежащих в основе Core Data, таких как faulting и совместимость моделей данных.

В следующем выпуске этой серии статей вы узнаете как для создавать и обновлять взаимосвязи, также мы более глубоко рассмотрим класс NSFetchRequest. Кроме того, мы начнем использовать NSPredicate и NSSortDescriptor чтобы сделать наши fetch-запросы гибкими, динамичными и мощными.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.