Несмотря на то, что Core Data существует уже много лет на OS X и iOS, функция, которая была добавлена только недавно, является пакетным обновлением. Разработчики просили эту функцию в течение многих лет, и Apple, наконец, нашла способ интегрировать ее в Core Data. В этом руководстве я покажу вам, как работают пакетные обновления и что они означают для платформы Core Data.
1. Проблема
Core Data отлично справляется с управлением графами объектов. Даже сложные графы объектов со многими сущностями и отношениями не являются большой проблемой для Core Data. Однако у Core Data есть несколько слабых мест, одним из которых является обновление большого количества записей.
Проблема проста для понимания. Всякий раз, когда вы обновляете запись, Core Data загружает запись в память, обновляет запись и сохраняет изменения в постоянном хранилище, например, в базе данных SQLite.
Если базовым данным требуется обновить большое количество записей, необходимо загрузить каждую запись в память, обновить запись и отправить изменения в постоянное хранилище. Если количество записей слишком велико, iOS просто выручит из-за нехватки ресурсов. Даже если устройство под управлением OS X может иметь ресурсы для выполнения запроса, оно будет медленным и занимать много памяти.
Альтернативный подход заключается в обновлении записей в пакетном режиме, но это также требует много времени и ресурсов. На iOS 7 это единственный вариант, который есть у разработчиков iOS. Это больше не относится к iOS 8 и OS X Yosemite.
2. Решение
На iOS 8 и выше и OS X Yosemite и выше можно напрямую поговорить с постоянным магазином и сказать ему, что вы хотите изменить. Обычно это включает обновление атрибута. Apple называет эту функцию пакетными обновлениями.
Хотя есть ряд подводных камней, на которые стоит обратить внимание. Core Data многое для вас делает, и вы можете даже не осознавать этого, пока не используете пакетные обновления. Валидация является хорошим примером. Поскольку Core Data выполняет пакетные обновления непосредственно в постоянном хранилище, например в базе данных SQLite, Core Data не может выполнять проверку данных, которые вы записываете в постоянное хранилище. Это означает, что вы отвечаете за то, чтобы не добавлять недопустимые данные в постоянное хранилище.
Когда вы будете использовать пакетные обновления? Apple рекомендует использовать эту функцию только в том случае, если традиционный подход требует слишком много ресурсов или времени. Если вам нужно пометить сотни или тысячи сообщений электронной почты как прочитанные, то пакетные обновления — это лучшее решение для iOS 8 и выше и OS X Yosemite и выше.
3. Как это работает?
Чтобы проиллюстрировать, как работают пакетные обновления, я предлагаю вернуться к Done , простому приложению Core Data, которое управляет списком дел. Мы добавим кнопку на панель навигации, которая пометит каждый элемент в списке как выполненный.
Шаг 1: Настройка Projet
Скачайте или клонируйте проект из GitHub и откройте его в Xcode 7. Запустите приложение в симуляторе и добавьте несколько задач.
Шаг 2. Создание элемента кнопки панели
Откройте ViewController.swift и объявите свойство checkAllButton
типа UIBarButtonItem
вверху.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
let ReuseIdentifierToDoCell = «ToDoCell»
@IBOutlet weak var tableView: UITableView!
var managedObjectContext: NSManagedObjectContext!
var checkAllButton: UIBarButtonItem!
…
}
|
Инициализируйте элемент кнопки панели в viewDidLoad()
класса ViewController
и установите его в качестве элемента кнопки левой панели элемента навигации.
1
2
3
4
5
|
// Initialize Check All Button
checkAllButton = UIBarButtonItem(title: «Check All», style: .Plain, target: self, action: «checkAll:»)
// Configure Navigation Item
navigationItem.leftBarButtonItem = checkAllButton
|
Шаг 3. Реализация checkAll(_:)
Метод checkAll(_:)
довольно прост, но есть несколько предостережений, на которые стоит обратить внимание. Посмотрите на его реализацию ниже.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
func checkAll(sender: UIBarButtonItem) {
// Create Entity Description
let entityDescription = NSEntityDescription.entityForName(«Item», inManagedObjectContext: managedObjectContext)
// Initialize Batch Update Request
let batchUpdateRequest = NSBatchUpdateRequest(entity: entityDescription!)
// Configure Batch Update Request
batchUpdateRequest.resultType = .UpdatedObjectIDsResultType
batchUpdateRequest.propertiesToUpdate = [«done»: NSNumber(bool: true)]
do {
// Execute Batch Request
let batchUpdateResult = try managedObjectContext.executeRequest(batchUpdateRequest) as!
// Extract Object IDs
let objectIDs = batchUpdateResult.result as!
for objectID in objectIDs {
// Turn Managed Objects into Faults
let managedObject = managedObjectContext.objectWithID(objectID)
managedObjectContext.refreshObject(managedObject, mergeChanges: false)
}
// Perform Fetch
try self.fetchedResultsController.performFetch()
} catch {
let updateError = error as NSError
print(«\(updateError), \(updateError.userInfo)»)
}
}
|
Создать пакетный запрос
Мы начинаем с создания экземпляра NSEntityDescription
для сущности Item и используем его для инициализации объекта NSBatchUpdateRequest
. Класс NSBatchUpdateRequest
является подклассом NSPersistentStoreRequest
.
1
2
3
4
5
|
// Create Entity Description
let entityDescription = NSEntityDescription.entityForName(«Item», inManagedObjectContext: managedObjectContext)
// Initialize Batch Update Request
let batchUpdateRequest = NSBatchUpdateRequest(entity: entityDescription!)
|
Мы устанавливаем тип результата запроса пакетного обновления на .UpdatedObjectIDsResultType
, значение NSBatchUpdateRequestResultType
перечисления NSBatchUpdateRequestResultType
. Это означает, что результатом запроса пакетного обновления будет массив, содержащий идентификаторы объектов, экземпляры класса NSManagedObjectID
, записей, которые были изменены запросом пакетного обновления.
1
2
|
// Configure Batch Update Request
batchUpdateRequest.resultType = .UpdatedObjectIDsResultType
|
Мы также заполняем свойство propertiesToUpdate
запроса на пакетное обновление. Для этого примера мы устанавливаем propertiesToUpdate
в словарь, содержащий один ключ "done"
со значением NSNumber(bool: true)
. Это означает, что каждая запись элемента будет сделана , что мы и ищем.
1
2
|
// Configure Batch Update Request
batchUpdateRequest.propertiesToUpdate = [«done»: NSNumber(bool: true)]
|
Выполнить запрос на пакетное обновление
Несмотря на то, что пакетные обновления обходят контекст управляемого объекта, выполнение запроса на пакетное обновление выполняется путем вызова executeRequest(_:)
для экземпляра NSManagedObjectContext
. Этот метод принимает один аргумент, экземпляр класса NSPersistentStoreRequest
. Поскольку executeRequest(_:)
является методом выбрасывания, мы выполняем метод в операторе do-catch
.
1
2
3
4
5
6
7
8
|
do {
// Execute Batch Request
let batchUpdateResult = try managedObjectContext.executeRequest(batchUpdateRequest) as!
} catch {
let updateError = error as NSError
print(«\(updateError), \(updateError.userInfo)»)
}
|
Обновление контекста управляемого объекта
Даже если мы передаем запрос на пакетное обновление в контекст управляемого объекта, контекст управляемого объекта не знает об изменениях в результате выполнения запроса на пакетное обновление. Как я упоминал ранее, он обходит контекст управляемого объекта. Это дает пакетные обновления их мощности и скорости. Чтобы исправить эту проблему, нам нужно сделать две вещи:
- превратить управляемые объекты, которые были обновлены при пакетном обновлении, в ошибки
- скажите контроллеру полученных результатов выполнить выборку для обновления пользовательского интерфейса
Это то, что мы делаем в следующих нескольких строках метода checkAll(_:)
в предложении do-catch
оператора do-catch
. Если запрос пакетного обновления выполнен успешно, мы извлекаем массив экземпляров NSManagedObjectID
из объекта NSBatchUpdateResult
.
1
2
|
// Extract Object IDs
let objectIDs = batchUpdateResult.result as!
|
Затем мы objectIDs
массив objectIDs
, запрашиваем контекст управляемого объекта для соответствующего экземпляра NSManagedObject
и превращаем его в ошибку, вызывая refreshObject(_:mergeChanges:)
, передавая управляемый объект в качестве первого аргумента. Чтобы принудить управляемый объект к ошибке, мы передаем false
в качестве второго аргумента.
1
2
3
4
5
6
7
8
|
// Extract Object IDs
let objectIDs = batchUpdateResult.result as!
for objectID in objectIDs {
// Turn Managed Objects into Faults
let managedObject = managedObjectContext.objectWithID(objectID)
managedObjectContext.refreshObject(managedObject, mergeChanges: false)
}
|
Извлечение обновленных записей
Последний шаг — сообщить контроллеру полученных результатов выполнить выборку для обновления пользовательского интерфейса. Если это не удалось, мы ловим ошибку в предложении do-catch
оператора do-catch
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
do {
// Execute Batch Request
let batchUpdateResult = try managedObjectContext.executeRequest(batchUpdateRequest) as!
// Extract Object IDs
let objectIDs = batchUpdateResult.result as!
for objectID in objectIDs {
// Turn Managed Objects into Faults
let managedObject = managedObjectContext.objectWithID(objectID)
managedObjectContext.refreshObject(managedObject, mergeChanges: false)
}
// Perform Fetch
try self.fetchedResultsController.performFetch()
} catch {
let updateError = error as NSError
print(«\(updateError), \(updateError.userInfo)»)
}
|
Хотя это может показаться громоздким и довольно сложным для легкой операции, имейте в виду, что мы обходим стек основных данных. Другими словами, нам нужно позаботиться о том, чтобы домашние хозяйства обычно выполнялись Core Data.
Шаг 4: Построить и запустить
Создайте проект и запустите приложение в симуляторе или на физическом устройстве. Нажмите или коснитесь элемента панели кнопок справа, чтобы проверить каждый элемент списка в списке.
Вывод
Пакетные обновления являются отличным дополнением к платформе Core Data. Это не только отвечает потребностям, которые испытывали разработчики в течение многих лет, это не сложно реализовать, если вы помните несколько основных правил. В следующем уроке мы подробнее рассмотрим пакетное удаление, еще одну особенность платформы Core Data, которая была добавлена только недавно.