Core data что это
Перейти к содержимому

Core data что это

  • автор:

Core data что это

Core Data — фреймворк от компании Apple, встроенный в операционную систему iOS, MacOS, который позволяет разработчику взаимодействовать с базой данных. Был представлен компанией Apple c анонсом Mac OS X 10.4 Tiger и iOS с iPhone SDK 3.0. Позволяет данным быть организованными в СущностьАтрибутЗначение (EAV). Управление данными может быть осуществлено с помощью манипуляций сущностей и их взаимосвязей.

Структура

  • Managed Object Context— компонент с которым идет взаимодействие, каждый раз, когда идет сохранение или перезапись;
  • Persistent Store Coordinator— выполняет цель хранения данных;
  • Managed Object Model— модель БД;
  • Persistent Store— репозиторий, где хранятся данные;

Использование

Сore Data описывает данные, которые хранятся в iOS приложении, код может манипулировать для сохранения и записи данных в приложении. Модель БД создается в Interface Builder. Код пишется на Objective-C или Swift. Core Data организован в огромные классы.

Суть работы фреймворка, как внутренней БД проста: создание модели, и при добавлении новых элементов сохранение с помощью метода saveContext(); в модель с помощью ниже приведенных методов в таблице.

Приложение не может работать без БД, так как при выходе из него данные будут утрачены. Это и является главной целью данного фреймворка — хранение данных, есть так же и похожие: Realm, SQLite.

Формат хранения

Core Data может конвертировать данные в XML, бинарный код, SQLite для хранения. Core Data схемы стандартизированы.

Если на компьютере не установлен Xcode, то возможность прочитать модель данных есть.

Core data что это

Core Data — фреймворк для хранения и управления объектным графом вашей модели данных в приложениях для macOS и iOS.

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

С Core Data разработчикам не придется писать SQL-запросы для прямого взаимодействия между базой данных SQLite и остальным кодом приложения на Swift/Objective-С. Использование функциональной прослойки делает настройку взаимосвязей в сотни раз быстрее, одновременно уменьшая количество ошибок.

Технология COREDATA.

MVC і Core Data

Core Data — не БД и не библиотека с API для сохранения информации в базу данных. Это фреймворк, управляющий графом объектов, который в свою очередь — часть Модели из шаблона MVC.

MVC или Model-View-Controller — стандартная модель архитектуры для iOS-приложений. Каждая из трех частей шаблона отвечает за выполнение определенных задач:

база данных и основная логика приложения (обрабатывает запросы Контроллера и передает их в Представление);

Представление

UI, все, что видит и с чем взаимодействует пользователь (формирует запросы к Модели и отображает ее изменения);

Контроллер

программная связь между Моделью и Представлением (реагирует на действия пользователя и передает их в Модель).

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

3 причины использовать Core Data для своего проекта

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

Пример использования технологии Core Data в коде.

Разработчики могут загрузить Core Data, как компонент XCode. При этом создается готовая пустая модель данных, которую можно заполнять в удобном встроенном редакторе. Количество кода, который нужно написать руками, становится намного меньше, а сам процесс структурирования и нагляднее. При этом вся работа с данными, от оптимизации хранилища, до установки отношений между объектами и различных тестов совместимости, проходит «внутри» Core Data. Разработчикам не нужно будет вручную писать код для каждого действия, только использовать готовые инструменты.

Код приложения содержащий Core Data.

Стандарт отрасли

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

Заключение

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

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

Разработка мобильных приложений
Разработка мобильных приложений

Разрабатываем приложения, которые решают бизнес-задачи клиентов и упрощают жизнь пользователей. Даже если эти задачи — со звёздочкой. Доставляем их в каждую ладошку и карман.

Core Data в деталях

Недавно я начала работать над большим проектом с использованием Core Data. Обычное дело, что люди на проектах меняются, опыт теряется, а нюансы забываются. Углубить всех в изучение конкретного фреймворка невозможно — у всех полно своих рабочих проблем. Поэтому я подготовила небольшую презентацию, из тех пунктов, которые считаю важными или недостаточно освещенными в туториалах. Делюсь со всеми и надеюсь, что это поможет писать эффективный код и не делать ошибок. Предполагается, что вы уже немного в теме.

Начну с банального.

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

Объекты Core Data

image

Для создания хранилища в приложении используются классы NSPersistentStoreCoordinator или NSPersistentContainer. NSPersistentStoreCoordinator создает хранилище указанного типа на основе модели, можно указать размещение и дополнительные опции. NSPersistentContainer можно использовать с IOS10, дает возможность создания с минимальным количеством кода.

Работает это следующим образом: если по указанному пути существует база данных, то координатор проверяет ее версию и, при необходимости, делает миграцию. Если база не существует, то она создается на основании модели NSManagedObjectModel. Чтобы все это работало правильно, перед внесением изменений в модель создавайте новую версию в Xcode через меню Editor -> Add Model Version. Если вывести путь, то можно найти и открыть базу в эмуляторе.

Пример с NSPersistentStoreCoordinator

var persistentCoordinator: NSPersistentStoreCoordinator = < let modelURL = Bundle.main.url(forResource: "Test", withExtension: "momd") let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL!) let persistentCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel!) let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] let storeURL = URL(fileURLWithPath: documentsPath.appending("/Test.sqlite")) print("storeUrl = \(storeURL)") do < try persistentCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: [NSSQLitePragmasOption: ["journal_mode":"MEMORY"]]) return persistentCoordinator >catch < abort() >> () 

Пример с NSPersistentContainer

var persistentContainer: NSPersistentContainer = < let container = NSPersistentContainer(name: "CoreDataTest") container.loadPersistentStores(completionHandler: < (storeDescription, error) in print("storeDescription = \(storeDescription)") if let error = error as NSError? < fatalError("Unresolved error \(error), \(error.userInfo)") >>) return container > () 

Core Data использует 4 типа хранилища:

— SQLite
— Binary
— In-Memory
— XML (только для Mac OS)

Если, например, по соображениям безопасности Вы не хотите хранить данные в файловом виде, но при этом хотите использовать кеширование в течении сессии и данные в виде объектов, вполне подойдет хранилище типа «In-Memory». Собственно, не запрещается иметь в одном приложении несколько хранилищ разного типа.

Несколько слов хочется сказать об объекте NSManagedObjectContext. Вообще, Apple дает весьма туманную формулировку для NSManagedObjectContext — среда для работы с объектами Core Data. Все это от желания отмежеваться от ассоциаций с реляционными базами, и представить Core Data, как простое в использовании средство, не требующее понимания ключей, транзакций и прочей базданской атрибутики. Но на языке реляционных баз NSManagedObjectContext можно, в некотором смысле, назвать менеджером транзакций. Вы, наверное, заметили, что он имеет методы save и rollback, хотя скорее всего вы пользуетесь только первым.

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

Наследование

По непонятной мне причине, существует очень большое количество мануалов и примеров, где никак не используется наследование для Entity/NSManagedObject(таблиц). Между тем, это очень удобный инструмент. Если вы не используете наследование, то присваивать значения атрибутам (полям) можно только через KVС механизм, который, не выполняет проверку имен и типов атрибутов, а это легко может привести к ошибкам времени выполнения.

Переопределение классов для NSManagedObject делается в дизайнере Core Data:

image

Наследование и кодогенерация

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

image

image

Если вы хотите посмотреть автогенерируемый код, но при этом, не хотите добавлять файлы в проект, можно воспользоваться другим способом: установить у Entity опцию «Codegen». В этом случае код нужно поискать в ../DerivedData/…

image

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

Вот примерно такой код будет создан:

@objc public class Company: NSManagedObject

В swift @NSManaged имеет тот же смысл, что и dynamic в Objective C.
Core Data сама заботится о получении данных (имеет внутренние аксессоры) для атрибутов своих классов. Если у вас есть транзитные поля, то нужно добавлять функции для их расчета.

Классы, наследуемые, от NSManagedObject (таблицы), не имели до IOS10 «обычного» конструктора, в отличии от остальных классов. Чтобы создать объект типа Company, нужно было написать достаточно неповоротливую конструкцию с использованием NSEntityDescription. Сейчас появился более удобный метод инициализации через контекст (NSManagedObjectContext). Код ниже. Обратите внимание на преимущество наследования при присвоении атрибутов перед механизм KVC:

// 1 - создание записи через NSEntityDescription, присвоение значений через KVO let company1 = NSEntityDescription.insertNewObject(forEntityName: "Company", into: moc) company1.setValue("077456789111", forKey: "inn") company1.setValue("Натура кура", forKey: "name") // 2 - создание записи через NSEntityDescription, присвоение значений через свойства let company2 = NSEntityDescription.insertNewObject(forEntityName: "Company", into: moc) as! Company company2.inn = "077456789222" company2.name = "Крошка макарошка" // 3 - создание записи через инициализатор (IOS10+), присвоение значений через свойства let company3 = Company(context: moc) company3.inn = "077456789222" company3.name = "Крошка макарошка" 

Пространство имен для NSManagedObject

Еще одна вещь которая стоит отдельного упоминания — это пространство имен.

image

У вас не возникнет затруднений, если вы работаете на ObjectiveC или Swift. Обычно, это поле заполняется правильно по умолчанию. А вот в смешанных проектах, возможно, для вас станет сюрпризом, что для классов в swift и ObjectiveC нужно проставить разные опции. В Swift «Модуль» должен быть заполнен. Если это поле не будет заполнено, то к имени класса добавится префикс с названием проекта, что вызовет ошибку выполнения. В Objetive C «Модуль» оставляйте пустым, иначе NSManagedObject не будет найден при обращении к нему через имя класса.

Связи между объектами

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

image

  • Создаем связь на каждой стороне (таблице)
  • После этого становится доступным поле Inverse, его нужно заполнить в каждой таблице.

image

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

Также важно правильно указывать правило удаления. Правило удаления – это действие, которое будет выполнятся с данным объектом при удалении родительского объекта.

  • Cascade — удаление всех дочерних объектов, при удалении родительского.
  • Deny — запрет удаления родителя, если есть дочерний объект
  • Nullify — обнуление ссылки на родителя
  • No action — действие не указано, выдаст предупреждении при компиляции

Способы добавления дочерних сущностей в родительскую

1) Первый способ — это добавление через NSSet. Для примера добавим 2 сотрудника в компанию:

let set = NSMutableSet(); if let employee1 = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: moc) as? Employee < employee1.firstName = "Дима" employee1.secondName = "Васильев" set.add(employee1) >if let emploee2 = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: moc) as? Employee < employee2.firstName = "Наташа" employee2.secondName = "Ростова" set.add(employee2) >company.employee = set 

Этот способ удобен при первичной инициализации объекта или при заполнении базы. Тут есть небольшой нюанс. Если в компании уже были сотрудники, а вы присвоили новый set, то у бывших сотрудников обнулится ссылка на компанию, но они не будут удалены. Как вариант можно получить список сотрудников и работать уже с этим set’ом.

let set = company.mutableSetValue(forKey: "employee") 

2) Добавление дочерних объектов через идентификатор родителя

if let employee = NSEntityDescription.insertNewObject(forEntityName: "Employee", into: moc) as? Employee

Второй способ удобен при добавлении или редактировании дочернего объекта в
отдельной форме.

3) Добавление дочерних объектов через автогенерируемые методы

extension Company

Для полноты картины полезно знать про этот метод, но мне он как-то не пригодился, а лишний код я удаляю, чтобы не загромождать проект.

Запросы с условием по дочернему элементу

В Core Data вы не можете составить произвольный запрос между любыми данными, как мы это можем делать в SQL. Но между зависимыми объектами это следить несложно используя стандартный предикат. Ниже пример запроса, выбирающего все компании в который есть сотрудник с указанным именем:

public static func getCompanyWithEmployee(name: String) -> [Company] < let request = NSFetchRequest(entityName: self.className()) request.predicate = NSPredicate(format: "ANY employee.firstName = %@", name) do < if let result = try moc.fetch(request) as? [Company] < return result >> catch < >return [Company]() > 

Вызов метода в коде будет выглядеть так:

// выбрать комании, где работает Миша let companies = Company.getCompanyWithEmployee(name: "Миша") 

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

Настройка атрибутов (полей)

Вы, наверное заметили, что атрибуты Entity имеют несколько опций.
C опциональностью все понятно из названия.

Oпция «использовать скалярный тип» появилась в swif. В Objective C не используются скалярные типы для атрибутов, так как они не могут принимать значение nil. Попытка присвоить скалярное значение атрибуту через KVC вызовет ошибку выполнения. Отсюда становится понятным, почему типы атрибутов в Core Data не имеют строгого соответствия с типами языка. В swift, и в смешанных проектах атрибуты скалярного типа можно использовать.

Транзитные атрибуты – это расчетные поля, которые не сохраняются в базе. Их можно использовать для шифрования. Эти атрибуты получают значения через переопределенный аксессор, либо через присваивание примитивов по мере надобности (например, в переопределенных willSave и awakeFromFetch).

Аксессоры атрибутов:

Если вам не нужно использовать расчетные поля, например, делать шифрование или что-то другое, то можно вообще не задумываться о том, чем являются аксессуары атрибутов. Между тем операции получения и присвоения значений атрибутам не являются «атомарными». Чтобы понять, что я имею ввиду смотрите код ниже:

// чтение let name = company.name // чтение company.willAccessValue(forKey: "name") let name = company.primitiveValue(forKey: "name") company.didAccessValue(forKey: "name") // присвоение company.name = "Азбука укуса" // присвоение company.willChangeValue(forKey: "name") company.setPrimitiveValue("Азбука укуса", forKey: "name") company.didChangeValue(forKey: "name") 

Используйте примитивы в event’ах NSManagedObject вместо обычного присваивания, чтобы избежать зацикливания. Пример:

override func willSave() < let nameP = encrypt(field: primitiveValue(forKey: "name"), password: password) setPrimitiveValue(nameP, forKey: "nameC") super.willSave() >override func awakeFromFetch()

Если вдруг когда-то вам придется прикрутить в проект функцию awakeFromFetch, то вы удивитесь, что работает она весьма странно, а по факту вызывается она совсем не тогда, когда вы выполните запрос. Связано это с тем, что Core Data имеет весьма интеллектуальный механизм кеширования, и если выборка уже находится в памяти (например, по причине того, что вы только что заполнили эту таблицу), то метод вызываться не будет. Тем не менее, мои эксперименты показали, что в плане вычисляемых значений, можно смело положиться на использование awakeFromFetch, об этом же говорит документация Apple. Если же для тестирования и разработки вам нужно принудительно вызвать awakeFromFetch, добавьте перед запросом managedObjectContext.refreshAllObjects().

Спасибо всем, кто дочитал до конца.

  • Разработка под iOS
  • Swift

Core Data: Часть 1

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

  1. создавать модели данных, которые вы хотите хранить в Core Data, используя Xcode редактор моделей;
  2. добавлять новые записи в Core Data;
  3. получать записи из Core Data;
  4. отображать полученные записи из Core Data в table view.

Вы так же получите представление о том, что происходит за кулисами Core Data и как вы можете взаимодействовать с различными подвижными его кусочками. Самое время начать создание своего приложения!

Поехали!

Открывайте Xcode и создайте новый проект iPhone, основанный на шаблоне Single View Application. Назовите его HitList и поставьте галочку напротив Use Core Data:

Галочка, поставленная напротив Use Core Data говорит Xcode создать шаблонный код, который известен как Core Data Stack в AppDelegate.swift. Core Data Stack состоит из набора объектов, которые облегчают сохранение и получение информации из Core Data.

Заметка

Не все шаблоны Xcode имеют опцию использования Core Data. В Xcode 6 этими шаблонами являются только Master-Detail Application и Single View Application.

Идея этого приложения очень проста. Это будет table view с вашим личным списком хитов (или имен). Вы сможете добавлять имена в этот список, используя Core Data, чтобы ваши данные были сохранены между сессиями.

Кликните на Main.storyboard, чтобы открыть его в interface builder. Выберите его единственный View Controller и измените его size class на Regular Height и Compact Width, чтобы он соответствовал портретному режиму iPhone.

Далее вставьте view controller в navigation controller. В Editor menu выберите Embed In…\ Navigation Controller.

Затем перетащите Table View из библиотеки объектов на ваш view controller так, чтобы он полностью перекрывал его.

После, перетащите из библиотеки Bar Button Item и разместите его на панели навигации (navigation bar) вашего view controller. Далее сделайте дабл-клик на добавленной кнопке, чтобы изменить ее имя и запишите Add. Теперь ваш холст должен выглядеть вот так:

Каждый раз, когда вы будете нажимать Add, в правом верхнем углу будет появляться окно, в котором вы сможете вписывать новое название в текстовое поле. При нажатии на это уведомление ваши данные будут сохраняться, а table view обновляться, отображая последние внесенные изменения.

До того, как вы сможете это сделать, вам нужно сделать ваш view controller как источник данных (data source) для вашего table view. Зажмите CTRL и перетащите курсор от table view на ваш view controller и отпустите. В появившемся окне выберите dataSource:

Если вам интересно, то мы не будем устанавливать ваш view controller в качестве делегата, так как у нас не будет происходить никаких действий при нажатии на ячейку таблицы. Проще не придумаешь!

Откройте Assistant Editor: либо просто нажав на центральную кнопку в панели инструментов редактора, либо, просто нажав Command+Option+Enter. Зажмите CTRL и перетащите от table view в ViewController.swift, внутри которого создайте IBOutlet с именем tableView. В итоге у вас должно получиться вот что:

@IBOutlet weak var tableView: UITableView!

С зажатым CTRL перетащите от кнопки Add в ViewController.swift, но в этот раз создайте метод IBAction, и название метода как addName:

@IBAction func addName(sender: AnyObject)

Теперь вы можете ссылаться на ваш table view и на вашу кнопку в вашем коде. Далее мы настроим модель для вашего table view. Добавьте следующее свойство в ViewController.swift:

//вставьте его под IBOutlet вашего tableView var names = [String]()

Массив names, который будет отображаться в table view.

Замените реализацию метода viewDidLoad:

override func viewDidLoad()

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

Все так же в ViewController.swift объявите, что ViewController подписан под протокол UITableViewDataSource, изменив объявление класса:

//Добавьте UITableViewDataSource к объявлению класса class ViewController: UIViewController, UITableViewDataSource 

Сразу же вы получите ошибку, что ваш ViewController не соответствует протоколу.

Под viewDidLoad реализуйте методы, чтобы исправить ошибку:

// MARK: UITableViewDataSource func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int < return names.count >func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell

Если вы когда-либо работали с UITableView, то этот код покажется вам очень знакомым. Первый метод говорит нам о том, что эта таблица будет иметь столько строк, сколько имен у нас находится в массиве names.

Второй метод tableView(_:cellForRowAtIndexPath:)вытаскивает из очереди ячейки таблицы и заполняет их соответствующими значениями из массива names.

Пока не запускайте приложение. Сначала вам нужно задать способ ввода значений, чтобы table view мог отобразить их.

Реализуем addName IBAction, которые вы внесли в ваш код ранее, путем перетаскивания (с зажатым CTRL).

//Реализация addName IBAction @IBAction func addName(sender: AnyObject) < var alert = UIAlertController(title: "New name", message: "Add a new name", preferredStyle: .Alert) let saveAction = UIAlertAction(title: "Save", style: .Default) < (action: UIAlertAction!) ->Void in let textField = alert.textFields![0] as! UITextField self.names.append(textField.text) self.tableView.reloadData() > let cancelAction = UIAlertAction(title: "Cancel", style: .Default) < (action: UIAlertAction!) ->Void in > alert.addTextFieldWithConfigurationHandler < (textField: UITextField!) ->Void in > alert.addAction(saveAction) alert.addAction(cancelAction) presentViewController(alert, animated: true, completion: nil) >

Каждый раз, когда вы нажимаете Add, срабатывает этот метод, который вызывает UIAlertController с текстовым полем и двумя кнопками: Save и Cancel. Кнопка Save принимает текст, который находится в текстовом поле и сохраняет его в массив names, а затем перегружает table view. Так как names является частью table view, то все, что вы напечатаете в этом текстовом поле, попадет в table view.

Теперь время запустить ваше приложение в первый раз, нажмите кнопку Add. Должен выскочить alert controller, который будет выглядеть вот так:

Добавьте несколько имен в список. В итоге у вас должно получиться что-то вроде этого:

Ваш table view будет отображать данные, а массив хранить их, но здесь не хватает постоянства — если вы экстренно выйдете из приложения или перезагрузите свое устройство, ваш список снова станет пустым.

Core Data предоставляет это постоянство, что означает, что ваши данные могут храниться более надежно, и они сохраняться, даже если вы перезагрузите ваше устройство. Вы еще не добавили ничего из Core Data, так что все ваши данные должны стереться, если вы выйдете из приложения. Давайте это проверим: нажмите кнопку “Home”, если вы тестируете это на реальном устройстве или «P», если вы используете симулятор. Это вернет вас на главный экран:

Теперь нажмите на HitList и вы увидите, что ваши данные все еще хранятся в приложении. Но как же так?

Когда вы нажимаете кнопку “Home” ваше приложение уходит на задний план. В этот момент операционная система запоминает все, что находится на данный момент в памяти, включая и ваш массив с именами. Аналогично, когда вы нажимаете на ваше приложение, но оно возвращается на передний план со всем хранящимися данными.

Apple анонсировала эти преимущества мультизадачности еще в iOS 4. Это, конечно, создает приятное впечатление для пользователей, но так же добавляет некоторые сложности для разработчиков под iOS. Но сохранился ли наш массив на самом деле?

На самом деле — нет. Если вы окончательно закрыли приложение или просто выключили телефон, то ваши имена просто исчезнут. Вы можете это проверить: нажмите быстро два раза на кнопку “Home”, чтобы у вас получилось вот так:

Переместите ваше приложение наверх, чтобы закрыть его. Отныне, вашего HitList в памяти не будет. Теперь перезапустите ваше приложение и вы убедитесь в том, что все имена, которые вы ввели, отсутствуют.

Если вы работали некоторое время с iOS, то вам наверняка знакома разница между flash-freezing (флеш-заморозка) и постоянным хранением, а так же сама работа мультизадачности. В понимании пользователей здесь нет никакой разницы. Для пользователя понятия “данные пока что еще здесь” и “данные сохранены” никакой разницы не имеют, так как есть доступ к данным.

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

Создаем модель ваших данных

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

До этого момента вы использовали старые добрые строки, для хранения имен в памяти, теперь же мы заменим эти строки на объекты Core Data.

Сперва мы создадим managed object model, которая определяет как Core Data отображает данные на диске. По умолчанию Core Data использует базу данных SQLite, как постоянное хранилище. Так что вы можете считать модель данных той же схемой базы данных.

Заметка

Вы будете время от времени пересекаться со словом “managed” (что значит управляемый) в этом туториале. Если вы увидите его в имени класса, например NSManagedObjectContext, то скорее всего вы имеете дело с Core Data. Слово “Managed” относится к управлению жизненного цикла объектов Core Data.

Однако, не думайте что все классы Core Data содержат это слово. Если при создании приложения вы поставили галочку напротив использования Core Data, то Xcode автоматически создал для вас HitList.xcdatamodeld.

Нажмите на HitList.xcdatamodeld, чтобы посмотреть что внутри. Как вы можете видеть, в Xcode есть очень мощный редактор модели данных, который выглядит вот так:

В этом редакторе есть много свойств и особенностей, но сейчас давайте сфокусируемся на создании единственной сущности — Core Data.

Нажмите на Add Entity в левом нижнем углу и создайте «Новую сущность». Сделайте двойной щелчок на «Сущности», чтобы изменить имя и напечатайте Person. У вас получится вот это:

Наверное вам интересно почему редактор использует определение “Entity” (сущность). Почему бы просто не назвать это новым классом? Все очень просто — в Core Data есть свой собственный словарик. Вот некоторые из терминов, которые использует Core Data:

  1. entity (сущность) — определение класса в Core Data. Классический пример Employee или Company. В отношении базы данных сущность соответствует таблице.
  2. attribute (атрибут) — кусок информации, прикрепленный к данной сущности. Например, сущность Employee может иметь такие атрибуты как: имя, должность и жалование. В базе данных атрибут соответствует ячейке таблицы.
  3. relationship (связь) — соединение между несколькими сущностями. В Core Data связи, находящиеся между двумя сущностями называются to-one связи, а между несколькими сущностями — to-many связями. Например, Manager может иметь to-many связи с несколькими Employee, в то время как каждый Employee имеет to-one связь с Managed.

Заметка

Как вы наверное заметили, сущности похожи на классы. В то время как атрибуты/связи напоминают нам свойства. Так какая же разница? В Core Data вы можете считать сущность определением класса, а управляемые объекты (managed objects) экземплярами класса.

Теперь, когда вы понимаете как атрибуты соотносятся с моделью, давайте добавим атрибут для Person. Выберите Person с левой стороны и нажмите значок (+) под Attributes.

Установите имя атрибута на name, а тип на String:

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

Сохранение в Core Data

Импортируйте модуль Core Data вверху ViewController.swift:

//Добавьте под "import UIKit" import CoreData

Возможно вам приходилось подключать фреймворки вручную во время фазы Build, когда вы работали с Objective-C. Но со Swift ключевого слова import вполне достаточно, чтобы начать использовать Core Data API в вашем коде.

Затем замените модель table view на следующую:

//Измените [String] на [NSManagedObject] var name = [NSManagedObject]()

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

//замените оба метода UITableViewDataSource func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int < return people.count >func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell

Самые большие изменения произошли в методе cellForRowAtIndexPath. Вместо заполнения ячеек соответствующими строками, теперь мы заполняем их соответствующими NSManagedObject.

Обратите внимание как вы берете атрибут из NSManagedObject. Это происходит здесь:

cell.textLabel!.text = person.valueForKey("name") as? String

Почему вам это нужно делать? Так получается, что NSManagedObject не имеет представления об атрибуте name, который вы определи в модели данных, таким образом мы не можем напрямую к нему обратиться. Единственный способ, который предоставляет нам Core Data для чтения — это KVC (key-value coding).

Заметка:

Если вы только начали программировать для iOS, то возможно вы не знакомы с KVC. KVC — механизм в Cocoa и Cocoa Touch для доступа к свойствам непрямым путем через использование строк, для определения свойств. В этом случае KVC заставляет вести себя NSManagedObject как словарь. KVC — механизм доступный всем классам, которые произошли от NSObject, включая и NSManagedObject. У вас не получится использовать KVC для доступа к свойствам в Swift, для объектов, которые произошли не от NSObject.

Теперь, давайте заменим действие Save в методе addName @IBAction следующим:

let saveAction = UIAlertAction(title: "Save", style: .Default) < (action: UIAlertAction!) ->Void in let textField = alert.textFields![0] as! UITextField self.saveName(textField.text) self.tableView.reloadData() >

Теперь мы передаем текст из текстового поля, при помощи нового метода saveName. Добавьте saveName @IBAction к ViewController.swift как показано ниже:

func saveName(name: String) < //1 let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate let managedContext = appDelegate.managedObjectContext! //2 let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: managedContext) let person = NSManagedObject(entity: entity!, insertIntoManagedObjectContext:managedContext) //3 person.setValue(name, forKey: "name") //4 var error: NSError? if !managedContext.save(&error) < println("Could not save \(error), \(error?.userInfo)") >//5 people.append(person) >

Тут у нас и появляется Core Data! Вот что делает код:

  1. До того как вы сможете что-либо сохранить или получить из вашей Core Data, вам все еще нужно поработать с NSManagedObjectContext. Считайте, что управляемый контекст объекта — это “блокнот” для работы с управляемыми объектами.
  2. Считайте, что сохранение нового управляемого объекта в Core Data двухступенчатым процессом. Первая ступень — внесение вашего управляемого объекта в контекст. Вторая ступень наступает после того, как вы убедились в том, что ваш объект такой, каким вы хотите его видеть. Далее вы сохраняете изменения в вашем контексте управляемого объекта на диск.
  3. Xcode уже сгенерировал контекст управляемого объекта как часть шаблона проектирования, но это происходит только в том случае, если вы поставили галочку напротив пункта Use Core Data. Этот контекст управляемого объекта живет в качестве свойства делегата приложения. Для того чтобы получить доступ к нему, вам сначала нужно сослаться на делегата приложения.
  4. Вы создаете новый управляемый объект и вставляете его в контекст. Вы можете сделать это за один шаг, при помощи назначенного инициализатора NSManagedObject: init(entity:insertIntoManagedObjectContext:).
  5. Вам может быть интересно, что такое NSEntityDescription. Вспомните, что немного ранее мы говорили о том, что NSManagedObject является классом, который может менять форму и может отображать любую сущность. Описание сущности — фрагмент, который соединяет определение сущности из вашей модели данных с экземпляром NSManagedObject во время исполнения.
  6. При помощи NSManagedObject вы можете установить атрибут name, используя KVC. Вы должны написать ваш ключ KVC (“name” в нашем случае) точно так, как он появляется в модели данных. В противном случае ваше приложение прекратит работу во время исполнения.
  7. Вы подтверждаете свои изменения в person и сохраняете их на диск, вызывая метод save у контекста управляемого объекта. Обратите внимание, что save принимает один параметр, который является указателем NSError. Если когда-нибудь произойдет ошибка сохранения, то вы можете ее инспектировать и уведомить о ней пользователя, если в этом есть такая необходимость.
  8. Поздравляем! Ваш новый управляемый объект был расположен на постоянное хранение в Core Data. Внесите еще один новый управляемый объект в массив people, чтобы он отобразился после перезагрузки table view.

Это немного сложнее чем массив строк, но не все так плохо. Некоторый код (получение контекста управляемого объекта и сущности) мог бы быть помещен в ваш собственный метод init или в viewDidLoad, откуда мог бы быть снова использован. Для простоты вы делаете это все в одном методе и за один раз.

Запустите ваше приложение и добавьте в него несколько имен:

Заметка

Если ваше приложение прекратило работу из-за ошибки, когда вы пытались добавить первое имя, то проверьте еще раз, переименовали ли вы в Core Data: Entity в Person, Person в HitList.xcdatamodeld. Если так оно и есть, то удалите приложение с симулятора и запустите его заново, но только после того, как вы поменяете имя на Person.

Если файлы фактически хранятся в Core Data, то наше приложение HitList должно пройти тест на сохранность данных. Дважды нажмите на “Home” и завершите работу нашего приложения.

Затем запустите его заново. Что случилось? Ваша table view пуста:

Вы сохранили ваши данные в Core Data, но после повторного запуска приложения массив people пуст! На самом деле данные сидят и ждут, но вы их пока не получили.

Получение из Core Data

Для получения данных их постоянного хранения в контекст управляемого объекта, вам нужно вызвать их. Добавьте следующий метод в ViewController.swift

override func viewWillAppear(animated: Bool) < super.viewWillAppear(animated) //1 let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate let managedContext = appDelegate.managedObjectContext! //2 let fetchRequest = NSFetchRequest(entityName:"Person") //3 var error: NSError? let fetchedResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as? [NSManagedObject] if let results = fetchedResults < people = results >else < println("Could not fetch \(error), \(error!.userInfo)") >>

Разберем этот код пошагово:

  1. Как мы говорили в прошлой секции, до того как вы сможете делать что-либо с Core Data, вам нужен контекст управляемого объекта. Вот вы его и получаете.
  2. Класс NSFetchRequest своим именем подсказывает нам, что он ответственен за получение данных из Core Data. Запрос на получение данных — очень гибкий и мощный инструмент. Вы можете построить запрос таким образом, что получите данные, удовлетворяющие определенным критериям. Например, вы можете написать: «Выдай-ка нам всех работников, работающих в Москве, и которые не меняли работу по меньшей мере 3 года».
  3. Запросы на получение имеют квалификаторы, которые уточняют результаты, возвращаемые ими. На данный момент вам нужно знать, что NSEntityDescription является одним из таких квалификаторов (обязательный).
  4. Инициализируя с помощью метода, init(entityName:) возвращает нам все объекты конкретной сущности. Это именно то, что мы сделали, чтобы получить все сущности Person.
  5. Вы передаете запрос в контекст управляемого объекта, чтобы он сделал всю работу.
  6. executeFetchRequest(_:error:) возвращает опциональный массив управляемых объектов, которые удовлетворяют критерию, заданному в запросе.

Заметка

Если объектов, удовлетворяющих критерию нет, то метод возвращает опциональный пустой массив. Если произошла ошибка при получении данных, то метод вернет опциональное значение, которое будет равно nil. Если такое случилось, то вы можете проинспектировать NSError и соответствующе отреагировать.

Запустите ваше приложение еще раз. Теперь вы увидите все ранее введенные вами имена (что-то типа “asd”, “qwe” и т.д.):

Ваши данные восстали из мертвых, не так ли? Добавьте еще парочку и перезапустите приложение. Все ваши имена должны быть отображены. Но если вы удалите ваше приложение, то соответственно все ваши имена сотрутся.

Здесь вы можете скачать наш конечный проект!

Что дальше?

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

Урок подготовил: Иван Акулов

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *