1. Введение
Ранее в этой серии мы создали простое приложение Done , чтобы узнать больше о классе NSFetchedResultsController
. В этом проекте мы использовали кодирование значения ключа (KVC) и наблюдение значения ключа (KVO) для создания и обновления записей. Это прекрасно работает, но с того момента, как ваш проект будет иметь какую-либо сложность, вы быстро столкнетесь с проблемами. Синтаксис KVC не только многословен, valueForKey(_:)
и setValue(_:forKey:)
, но и может привести к ошибкам, которые являются результатом опечаток. Следующий фрагмент кода хорошо иллюстрирует эту проблему.
1
2
3
4
|
record.setValue(NSDate(), forKey: «createdat»)
record.setValue(NSDate(), forKey: «CreatedAt»)
record.setValue(NSDate(), forKey: «createdAt»)
record.setValue(NSDate(), forKey: «CREATEDAT»)
|
Каждый оператор в приведенном выше фрагменте кода возвращает свой результат. Фактически, каждый оператор приведет к исключению, кроме третьего, который использует правильный ключ, указанный в модели данных.
Вышеуказанная проблема легко решается с помощью строковых констант, но я не пытаюсь это сделать. Кодирование значения ключа — это здорово, но оно многословно и его трудно читать, если вы привыкли к точечному синтаксису Swift. Чтобы упростить работу с экземплярами NSManagedObject
, лучше создать подкласс NSManagedObject
для каждой сущности модели данных, и об этом вы узнаете в этой статье.
2. Подклассы NSManagedObject
Чтобы сэкономить время, мы вернемся к Done , приложению, которое мы создали ранее в этой серии. Загрузите его с GitHub и откройте в Xcode.
Создать подкласс NSManagedObject
очень легко. Хотя можно вручную создать подкласс NSManagedObject
для сущности, проще позволить XCode выполнить всю работу за вас.
Откройте модель данных проекта Done.xcdatamodeld и выберите объект Item . Выберите New> File … из меню File Xcode, выберите шаблон подкласса NSManagedObject из раздела Core Data и нажмите Next .
Установите флажок правильной модели данных, Готово , в списке моделей данных и нажмите Далее .
На следующем шаге вас попросят выбрать сущности, для которых вы хотите создать подкласс NSManagedObject
. Проверьте флажок объекта Item и нажмите Next .
Выберите расположение для хранения файлов классов подкласса NSManagedObject
и убедитесь, что установлен флажок Использовать скалярные свойства для примитивных типов данных . При работе с Objective-C этот параметр является важным фактором. Но для Swift лучше всего проверить это, поскольку он делает ваш код более читабельным и менее сложным. Не забудьте установить для языка значение Swift и нажать кнопку « Создать», чтобы создать подкласс NSManagedObject
для объекта Item .
3. NSManagedObject
Anatomy
Перейдите к файлам Xcode, созданным для вас, и посмотрите на их содержимое. Xcode должен создать для вас два файла:
- Item.swift
- Пункт + CoreDataProperties.swift
Если вы используете более раннюю версию Xcode, возможно, Xcode создал только один файл. Я рекомендую использовать Xcode 7 — предпочтительно Xcode 7.1 или выше — чтобы следовать. Содержимое Item.swift должно быть довольно легко понять.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
//
// Item.swift
// Done
//
// Created by Bart Jacobs on 24/10/15.
// Copyright © 2015 Envato Tuts+.
//
import Foundation
import CoreData
class Item: NSManagedObject {
// Insert code here to add functionality to your managed object subclass
}
|
Как вы можете видеть, Xcode создал для нас класс Item
, который наследуется от NSManagedObject
. Комментарий, который XCode добавил к реализации класса, важен. Если вы хотите добавить функциональность в класс, вы должны добавить его здесь. Давайте теперь посмотрим на содержимое Item + CoreDataProperties.swift .
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
//
// Item+CoreDataProperties.swift
// Done
//
// Created by Bart Jacobs on 24/10/15.
// Copyright © 2015 Envato Tuts+.
//
// Choose «Create NSManagedObject Subclass…» from the Core Data editor menu
// to delete and recreate this implementation file for your updated model.
//
import Foundation
import CoreData
extension Item {
@NSManaged var createdAt: NSTimeInterval
@NSManaged var done: Bool
@NSManaged var name: String?
}
|
Есть ряд важных деталей и несколько новых концепций. Первое, на что нужно обратить внимание, это то, что этот файл определяет расширение класса Item
. Расширение объявляет три свойства, которые соответствуют атрибутам сущности Item , которые мы определили в модели данных. @NSManaged
аналогичен @dynamic
в Objective-C. @NSManaged
сообщает компилятору, что хранение и реализация этих свойств будут обеспечиваться во время выполнения. Хотя это может звучать замечательно, в документации Apple четко говорится, что @NSManaged
следует использовать только в контексте Core Data.
Если это звучит немного @NSManaged
, помните, что @NSManaged
требуется для Core Data, чтобы он работал, а атрибут @NSManaged
должен использоваться только для подклассов NSManagedObject
.
Что произойдет, если вы измените сущность и снова NSManagedObject
файлы для подкласса NSManagedObject
? Это хороший вопрос, и XCode предупреждает вас об этом сценарии. В верхней части Item + CoreDataProperties.swift , ниже заявления об авторских правах, Xcode добавил комментарий для пояснения.
1
2
|
// Choose «Create NSManagedObject Subclass…» from the Core Data editor menu
// to delete and recreate this implementation file for your updated model.
|
Всякий раз, когда вы генерируете файлы для объекта, XCode будет заменять только файлы с расширением. Другими словами, если вы добавляете атрибут к сущности Item и генерируете файлы для подкласса NSManagedObject
, Xcode заменяет Item + CoreDataProperties.swift , оставляя Item.swift нетронутым. Вот почему Xcode говорит вам добавить функциональность в Item.swift . Это очень важно иметь в виду.
Типы свойств могут быть немного удивительными. Свойство name
имеет тип NSString?
потому что атрибут помечен как необязательный в модели данных. Свойство createdAt
имеет тип NSTimeInterval
, поскольку мы установили флажок Использовать скалярные свойства для примитивных типов данных . То же самое верно для свойства done
, которое имеет тип Bool
.
Если бы мы не установили флажок, свойство createdAt
будет иметь тип NSDate?
а свойство done
будет иметь тип NSNumber?
, Хотя может быть проще использовать экземпляры NSDate
, но, безусловно, менее практично работать с объектами NSNumber
если все, что вам нужно, это получить и установить логические значения.
4. Обновление проекта
Когда класс Item
готов к использованию, пришло время обновить проект, заменив все вхождения valueForKey(_:)
и setValue(_:forKey:)
.
ViewController
Откройте файл ViewController
класса ViewController
, перейдите к configureCell(_:atIndexPath:)
и обновите реализацию, как показано ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
func configureCell(cell: ToDoCell, atIndexPath indexPath: NSIndexPath) {
// Fetch Record
let record = fetchedResultsController.objectAtIndexPath(indexPath) as!
// Update Cell
if let name = record.name {
cell.nameLabel.text = name
}
cell.doneButton.selected = record.done
cell.didTapButtonHandler = {
record.done = !record.done
}
}
|
Мы сделали четыре изменения. Сначала мы изменили тип переменной record
на Item
. Метод objectAtIndexPath(_:)
класса NSFetchedResultsController
возвращает экземпляр AnyObject
. Однако, поскольку мы знаем, что контроллер полученных результатов возвращает экземпляр Item
мы принудительно понижаем результат, используя as!
оператор.
Мы также подставляем valueForKey(_:)
. Вместо этого мы используем свойства объекта record
, name
и done
. Благодаря точечному синтаксису Swift результат очень разборчивый.
Чтобы обновить запись, мы больше не вызываем setValue(_:forKey:)
. Вместо этого мы используем точечный синтаксис, чтобы установить свойство done
. Я уверен, что вы согласны с тем, что это намного элегантнее, чем использование прямого кодирования значения ключа. Нам также не требуется дополнительная привязка для свойства done
так как свойство done
имеет тип Bool
.
Помните, что KVC и KVO остаются неотъемлемой частью Core Data. Базовые данные используют valueForKey(_:)
и setValue(_:forKey:)
под капотом, чтобы выполнить работу.
AddToDoViewController
Нам также нужно внести несколько изменений в класс AddToDoViewController
. В методе save(_:)
нам сначала нужно обновить инициализацию экземпляра NSManagedObject
. Вместо инициализации экземпляра NSManagedObject
мы создаем экземпляр Item
.
1
2
3
4
5
|
// Create Entity
let entity = NSEntityDescription.entityForName(«Item», inManagedObjectContext: self.managedObjectContext)
// Initialize Record
let record = Item(entity: entity!, insertIntoManagedObjectContext: self.managedObjectContext)
|
Чтобы заполнить запись, мы используем точечный синтаксис вместо метода setValue(_:forKey:)
как показано ниже.
1
2
3
|
// Populate Record
record.name = name
record.createdAt = NSDate().timeIntervalSince1970
|
UpdateToDoViewController
Последний класс, который нам нужно обновить, это класс UpdateToDoViewController
. Давайте начнем с изменения типа свойства record
на Item!
,
01
02
03
04
05
06
07
08
09
10
11
12
13
|
import UIKit
import CoreData
class UpdateToDoViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
var record: Item!
var managedObjectContext: NSManagedObjectContext!
…
}
|
Это изменение приведет к предупреждению в классе ViewController
. Чтобы увидеть, что не так, откройте ViewController.swift и перейдите к prepareForSegue(_:sender:)
.
Мы запрашиваем у контроллера полученных результатов запись по выбранному пути индекса. Тип переменной record
— NSManagedObject
, но класс UpdateToDoViewController
ожидает экземпляр Item
. Решение очень простое, как вы можете видеть ниже.
1
2
3
4
5
6
7
8
|
if let indexPath = tableView.indexPathForSelectedRow {
// Fetch Record
let record = fetchedResultsController.objectAtIndexPath(indexPath) as!
// Configure View Controller
viewController.record = record
viewController.managedObjectContext = managedObjectContext
}
|
Вернитесь к классу UpdateToDoViewController
и обновите метод viewDidLoad()
как показано ниже.
1
2
3
4
5
6
7
|
override func viewDidLoad() {
super.viewDidLoad()
if let name = record.name {
textField.text = name
}
}
|
Нам также необходимо обновить метод save(_:)
, в котором мы заменяем setValue(_:forKey:)
на синтаксис точки.
1
2
|
// Update Record
record.name = name
|
Создайте проект и запустите приложение в симуляторе, чтобы увидеть, все ли по-прежнему работает, как ожидалось.
5. Отношения
Текущая модель данных не содержит никаких отношений, но давайте добавим несколько, чтобы увидеть, как выглядит подкласс NSManagedObject
со связями. Как мы видели в предыдущей статье этой серии, нам сначала нужно создать новую версию модели данных. Выберите модель данных в Навигаторе проекта и выберите « Добавить версию модели …» в меню « Редактор» . Установите для Version Version значение Done 2 и основывайте модель на текущей модели данных, Done . Нажмите Готово, чтобы создать новую версию модели данных.
Откройте Done 2.xcdatamodel , создайте новый объект с именем User и добавьте имя атрибута типа String . Добавьте элементы отношений и установите пункт назначения Item . Оставьте пока обратное отношение пустым. Выбрав взаимосвязь элементов , откройте инспектор модели данных справа и установите для типа взаимосвязи значение « Многие» . Пользователь может иметь более одного элемента, связанного с ним.
Выберите объект Item , создайте пользователя- отношения и установите для пункта назначения значение « Пользователь» . Установите обратное отношение к элементам . Это автоматически установит обратную связь отношений элементов сущности User . Обратите внимание, что пользовательские отношения не являются обязательными.
Прежде чем мы создадим подклассы NSManagedObject
для обеих сущностей, нам нужно сообщить модели данных, какую версию модели данных она должна использовать. Выберите Done.xcdatamodeld , откройте инспектор файлов справа и установите текущую версию модели на Done 2 . Когда вы сделаете это, дважды проверьте, что вы выбрали Done.xcdatamodeld , а не Done.xcdatamodel .
Выберите New> File … в меню File и выберите шаблон подкласса NSManagedObject в разделе Core Data . В списке моделей данных выберите Готово 2 .
Выберите обе сущности из списка сущностей. Поскольку мы изменили сущность Item , нам нужно NSManagedObject
расширение для соответствующего подкласса NSManagedObject
.
Item
Давайте сначала посмотрим на изменения недавно сгенерированного класса Item
. Как вы можете видеть ниже, расширение класса Item ( Item + CoreDataProperties.swift ) содержит одно дополнительное свойство user
типа User?
,
01
02
03
04
05
06
07
08
09
10
11
|
import Foundation
import CoreData
extension Item {
@NSManaged var createdAt: NSTimeInterval
@NSManaged var done: Bool
@NSManaged var name: String?
@NSManaged var user: User?
}
|
Вот как выглядит отношение To One в подклассе NSManagedObject
. Xcode достаточно умен, чтобы сделать вывод, что тип user
свойства — User?
, подкласс NSManagedObject
мы создали минуту назад. Даже если отношение пользователя помечено как необязательное в модели данных, тип user
свойства — User?
, Не ясно, является ли это преднамеренным или ошибкой в Xcode.
Реализация класса Item
( Item.swift ) не была обновлена Xcode. Помните, что Xcode не трогает этот файл, если он уже существует.
User
Благодаря тому, что мы узнали, класс User
легко понять. Посмотрите на расширение класса User ( User + CoreDataProperties.swift ). Первое, что вы заметите, это то, что тип свойства items
— NSSet?
, Это не должно быть сюрпризом, потому что мы уже знали, что Core Data использует наборы, экземпляры класса NSSet
, для хранения членов To Many отношения. Поскольку отношение помечено как необязательное в модели данных, типом является NSSet?
,
1
2
3
4
5
6
7
8
9
|
import Foundation
import CoreData
extension User {
@NSManaged var name: String?
@NSManaged var items: NSSet?
}
|
Вот как легко работать со связями в подклассах NSManagedObject
.
6. Миграции
Если вы соберете проект и запустите приложение в симуляторе, вы заметите, что приложение вылетает. Выходные данные в консоли XCode говорят нам, что модель данных, используемая для открытия постоянного хранилища, несовместима с той, которая использовалась для его создания. Причина этой проблемы подробно объясняется в предыдущей статье этой серии. Если вы хотите узнать больше о миграциях и о том, как безопасно изменить модель данных, я предлагаю вам прочитать эту статью .
Вывод
Подклассы NSManagedObject
очень распространены при работе с Core Data. Это не только повышает безопасность типов, но и значительно облегчает работу с отношениями.
В следующей части этой серии мы подробнее рассмотрим основные данные и параллелизм. Параллельность — сложная концепция практически для любого языка программирования, но знание того, каких ошибок следует избегать, делает ее гораздо менее пугающей.