Статьи

Основные данные стали лучше

Во время WWDC в этом году Apple представила ряд существенных улучшений Core Data, подняв структуру на новый уровень. В этой статье я увеличу:

  • Постоянные контейнеры
  • Поддержка Xcode 8 и Swift 3
  • Query Generations
  • Улучшения параллелизма

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

Команда Apple представила совершенно нового участника фреймворка, который позволяет с легкостью настроить и управлять стеком основных данных вашего приложения. Этот новый член является классом NSPersistentContainer .

Настройка NSPersistentContainer стека данных тривиальна NSPersistentContainer класса NSPersistentContainer . Вы можете инициализировать экземпляр, вызвав init(name:) . Имя, которое вы передаете инициализатору, используется постоянным контейнером для поиска модели данных в комплекте приложений, а также для именования постоянного хранилища приложения.

1
let persistentContainer = NSPersistentContainer(name: «MyApplication»)

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

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

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

Если вам нужно выполнить операцию Core Data в фоновом режиме, вы можете запросить у постоянного контейнера наличие частного контекста управляемого объекта, вызвав метод фабрики newBackgroundContext() . Для облегченных операций вы можете вызвать метод performBackgroundTask(_:) . Этот метод принимает замыкание, в котором вы можете выполнить фоновую операцию, используя закрытый контекст управляемого объекта.

Apple также представила класс NSPersistentStoreDescription . Он инкапсулирует информацию для создания и загрузки постоянного хранилища, например, местоположение постоянного хранилища, его конфигурацию и то, является ли оно постоянным хранилищем только для чтения.

Добавление постоянного хранилища в постоянный координатор хранилища намного более элегантно с использованием класса NSPersistentStoreDescription . Класс NSPersistentStoreCoordinator определяет новый метод addPersistentStore(with:completionHandler:) , который принимает экземпляр NSPersistentStoreDescription и обработчик завершения.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
// Create Persistent Store Description
let persistentStoreDescription = NSPersistentStoreDescription(url: url)
 
// Add Persistent Store to Persistent Store Coordinator
persistentContainer.persistentStoreCoordinator.addPersistentStore(with: persistentStoreDescription, completionHandler: { (persistentStoreDescription, error) in
    if let error = error {
        print(«Unable to Add Persistent Store»)
        print(«\(error), \(error.localizedDescription)»)
 
    } else {
        // Successfully Added Persistent Store
 
    }
}

Xcode 8 также включает в себя ряд изменений, которые значительно улучшают поддержку Core Data. Xcode теперь автоматически генерирует подклассы NSManagedObject для каждой сущности модели данных. И вместо того, чтобы добавлять файлы для подклассов в проект, загромождая проект, они добавляются в папку « Производные данные ». Это означает, что Xcode может убедиться, что они обновляются при изменении модели данных, и вам, разработчику, не нужно беспокоиться о файлах.

Как это работает? Если вы откроете модель данных вашего проекта в Xcode 8 и выберете объект, вы увидите новую опцию в инспекторе моделей данных справа. Поле Codegen в разделе Class — вот что нас интересует.

Генерация кода в Xcode 8

Начиная с Xcode 8.1, значение этого поля автоматически устанавливается на Определение класса . Это означает, что Xcode генерирует подкласс NSManagedObject для выбранной сущности. Если вы вносите какие-либо изменения в сущность, сгенерированный класс автоматически обновляется для вас. Вам не нужно ни о чем беспокоиться.

Генерация кода в Xcode 8

Вы можете отключить генерацию кода или вы можете указать Xcode генерировать только расширение (Swift) или категорию (Objective-C) для класса. Если вы выберете последний вариант, то вы отвечаете за создание подкласса NSManagedObject для сущности.

NSFetchedResultsType Core Data также определяет новый протокол NSFetchedResultsType , который значительно упрощает работу с платформой и делает ее более элегантной в Swift. Это то, что использовалось для создания нового управляемого объекта для объекта Category .

1
2
3
if let entity = NSEntityDescription.entity(forEntityName: «Category», in: managedObjectContext) {
    let category = Category(entity: entity, insertInto: managedObjectContext)
}

Начиная с iOS 10 и macOS 10.12, каждый подкласс NSManagedObject знает, к какому объекту он принадлежит.

1
category.entity

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

1
let category = Category(context: managedObjectContext)

Я уверен, что вы согласны, что это выглядит намного элегантнее и лаконичнее. Создание запросов на выборку также намного проще. Посмотрите на пример ниже.

1
let fetchRequest = Category.fetchRequest()

То, что я показываю вам, является лишь подмножеством улучшений, доступных в iOS 10, tvOS 10, macOS 10.12 и watchOS 3. Существует гораздо больше возможностей для изучения.

Наиболее важным объявлением, вероятно, является введение поколений запросов. Поколения запросов решают проблему, которая мучает Core Data с тех пор, как она была выпущена более десяти лет назад.

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

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

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

Поколения запросов предоставляют другое решение, которое решает корень проблемы. Идея проста. Выполнение немного сложнее.

Начиная с iOS 10 и macOS 10.12, можно назначить генерацию запроса для контекста управляемого объекта. Это означает, что контекст управляемого объекта взаимодействует со снимком данных, хранящихся в постоянном хранилище. Даже если другие контексты управляемого объекта вносят изменения (вставки, удаления и обновления), снимок контекста управляемого объекта остается неизменным.

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

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

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

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

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

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

Начиная с iOS 10 и macOS 10.12, координатор постоянного хранилища больше не берет блокировку, когда контекст управляемого объекта передает изменения в координатор постоянного хранилища. Вместо этого постоянное хранилище немедленно отправляет запросы координатора постоянного хранилища в само хранилище SQL. Вместо этого блокировка берется на уровне хранилища SQL.

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

Поскольку это решение реализовано на уровне хранилища SQL, оно доступно только в том случае, если ваше приложение использует хранилище SQLite в качестве постоянного хранилища. Если ваше приложение использует несколько постоянных хранилищ, то каждое из этих хранилищ должно быть хранилищем SQLite. Эта функция включена по умолчанию.

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

Чтобы узнать больше о Core Data, ознакомьтесь с сериями о Core Data и Swift здесь, на Envato Tuts +.

  • Основные данные
    Базовые данные и Swift: базовый стек данных
  • Основные данные
    Основные данные и Swift: модель данных
  • Основные данные
    Базовые данные и Swift: управляемые объекты и запросы на выборку
  • Основные данные
    Основные данные и Swift: отношения и дополнительная выборка
  • Основные данные
    Основные данные и Swift: NSFetchedResultsController
  • Основные данные
    Основные данные и Swift: больше NSFetchedResultsController