Статьи

Основные данные и Swift: пакетные обновления

Несмотря на то, что Core Data существует уже много лет на OS X и iOS, функция, которая была добавлена ​​только недавно, является пакетным обновлением. Разработчики просили эту функцию в течение многих лет, и Apple, наконец, нашла способ интегрировать ее в Core Data. В этом руководстве я покажу вам, как работают пакетные обновления и что они означают для платформы Core Data.

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

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

Если базовым данным требуется обновить большое количество записей, необходимо загрузить каждую запись в память, обновить запись и отправить изменения в постоянное хранилище. Если количество записей слишком велико, iOS просто выручит из-за нехватки ресурсов. Даже если устройство под управлением OS X может иметь ресурсы для выполнения запроса, оно будет медленным и занимать много памяти.

Альтернативный подход заключается в обновлении записей в пакетном режиме, но это также требует много времени и ресурсов. На iOS 7 это единственный вариант, который есть у разработчиков iOS. Это больше не относится к iOS 8 и OS X Yosemite.

На iOS 8 и выше и OS X Yosemite и выше можно напрямую поговорить с постоянным магазином и сказать ему, что вы хотите изменить. Обычно это включает обновление атрибута. Apple называет эту функцию пакетными обновлениями.

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

Когда вы будете использовать пакетные обновления? Apple рекомендует использовать эту функцию только в том случае, если традиционный подход требует слишком много ресурсов или времени. Если вам нужно пометить сотни или тысячи сообщений электронной почты как прочитанные, то пакетные обновления — это лучшее решение для iOS 8 и выше и OS X Yosemite и выше.

Чтобы проиллюстрировать, как работают пакетные обновления, я предлагаю вернуться к Done , простому приложению Core Data, которое управляет списком дел. Мы добавим кнопку на панель навигации, которая пометит каждый элемент в списке как выполненный.

Скачайте или клонируйте проект из GitHub и откройте его в Xcode 7. Запустите приложение в симуляторе и добавьте несколько задач.

Добавление дел

Откройте 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

Метод 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.

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

Проверка всех дел

Пакетные обновления являются отличным дополнением к платформе Core Data. Это не только отвечает потребностям, которые испытывали разработчики в течение многих лет, это не сложно реализовать, если вы помните несколько основных правил. В следующем уроке мы подробнее рассмотрим пакетное удаление, еще одну особенность платформы Core Data, которая была добавлена ​​только недавно.