Синхронизация данных приложения между устройствами является сложной и сложной задачей. К счастью, именно поэтому Apple создала iCloud. Из этой серии Tuts + Premium вы узнаете, как работает iCloud и как ваши приложения могут беспрепятственно обмениваться данными между несколькими устройствами.
Также доступно в этой серии:
- Работа с iCloud: Введение
- Работа с iCloud: хранилище ключей-значений
- Работа с iCloud: Хранение документов
- Работа с iCloud: интеграция основных данных
Во второй части этой серии я показал вам, как использовать хранилище ключей-значений iCloud для синхронизации небольших объемов пользовательских данных на нескольких устройствах. Несмотря на то, что хранилище ключей и значений простое в использовании и использовании, одним из недостатков является ограничение объема данных, которые могут быть сохранены. Помните, что каждое приложение может хранить только 1 МБ данных, а количество пар ключ-значение ограничено 1024. Как я упоминал в конце предыдущего урока, наш менеджер закладок может столкнуться с этим ограничением, если кто-либо из наших пользователей захочет сохранить много закладок.
Решением этой проблемы является переключение с iCloud Key-Value Storage на iCloud Document Storage для хранения закладок. Что касается дискового пространства, хранилище документов iCloud ограничено только хранилищем iCloud пользователя. Зная, что бесплатная учетная запись поставляется с 5 ГБ хранилища данных, iCloud Document Storage является идеальным решением для нашего менеджера закладок. В этом руководстве мы проведем рефакторинг менеджера закладок от использования хранилища ключей-значений iCloud до принятия хранилища документов iCloud.
Прежде чем мы начнем
Я хотел бы подчеркнуть, что перед прочтением этой статьи важно прочитать первую и вторую часть этой серии. В этом уроке мы проведем рефакторинг нашего менеджера закладок, опираясь на основы, которые мы заложили во второй части серии.
Несколько слов о UIDocument
С введением iCloud Apple также сделала UIDocument
доступным для разработчиков. Инженеры Apple создали UIDocument
с учетом iCloud. UIDocument
упрощает интеграцию iCloud для приложений на основе документов. Однако важно отметить, что UIDocument
делает гораздо больше, чем просто предоставляет простой в использовании API для интеграции с iCloud.
Приложения на основе документов сталкиваются с рядом проблем, таких как (1) чтение и запись данных с диска и на диск без блокировки пользовательского интерфейса, (2) сохранение данных на диск через соответствующие интервалы и (3) дополнительная интеграция с iCloud , UIDocument
предоставляет встроенные решения для этих задач.
Прежде чем мы начнем работать с UIDocument
, я хочу пояснить, что такое UIDocument
, а что нет. UIDocument
— это объект контроллера, который управляет одной или несколькими моделями так же, как UIViewController
контролирует и управляет одним или несколькими представлениями. UIDocument
не хранит никаких данных, но управляет объектами модели, которые содержат данные пользователя. Это важная концепция для понимания, которая станет понятнее, когда мы начнем рефакторинг нашего приложения для использования UIDocument
.
Еще одна важная концепция, которую необходимо понять, — это как операции чтения и записи работают при использовании UIDocument
. Если вы решили использовать UIDocument
в своем приложении, вам не нужно беспокоиться о блокировке основного потока при чтении или записи данных на диск. При использовании UIDocument
операционная система автоматически обрабатывает ряд задач для вас в фоновой очереди и гарантирует, что основной поток остается отзывчивым. Я хотел бы уделить минутку и объяснить каждую операцию более подробно, чтобы дать вам хорошее понимание различных движущихся частей, которые участвуют.
Начнем с чтения данных с диска. Операция чтения начинается с операции открытия, инициированной в очереди вызовов. Операция открытия инициируется при открытии документа путем отправки ему сообщения openWithCompletionHandler:. Мы передаем обработчик завершения, который вызывается после завершения всей операции чтения. Это важный аспект операций чтения и записи. Чтение или запись данных с диска или на диск может занять нетривиальное время, и мы не хотим делать это в главном потоке и блокировать пользовательский интерфейс. Фактическая операция чтения происходит в фоновой очереди, управляемой операционной системой. Когда операция чтения завершается, в документе вызывается метод loadFromContents: ofType: error:. Этот метод отправляет UIDocument
данные, необходимые для инициализации модели, которой он управляет. Обработчик завершения вызывается после завершения этого процесса, что означает, что мы можем ответить на загрузку документа, например, обновив пользовательский интерфейс содержимым документа.
Операция записи аналогична. Он начинается с операции сохранения, инициируемой в очереди вызовов, путем отправки saveToURL: forSaveOperation: завершениюHandler: объекту документа. Как и в случае операции чтения, мы передаем обработчик завершения, который вызывается после завершения операции записи. Запись данных на диск происходит в фоновой очереди. Операционная система запрашивает у UIDocument
моментальный снимок данных своей модели, отправляя ему сообщение UIDocument
: error:. Обработчик завершения вызывается после завершения операции записи, что дает нам возможность обновить пользовательский интерфейс.
UIDocument
является базовым классом и не предназначен для непосредственного использования. Нам нужно UIDocument
подкласс UIDocument
и адаптировать его к нашим потребностям. Другими словами, мы подкласс UIDocument
чтобы он знал о нашей модели и как управлять ею. В своей основной форме UIDocument
подклассов UIDocument
только переопределения loadFromContents: ofType: error: для чтения и contentForType : error: для записи.
Смущенный? Вы должны быть. Хотя UIDocument
делает жизнь намного проще, это продвинутый класс, и мы имеем дело со сложной темой. Однако я убежден, что вы получите хорошее представление о приложениях на основе документов, как только мы проведем рефакторинг нашего приложения.
Прежде чем мы продолжим, я хочу прояснить, каковы наши цели для этого урока. Основная цель — реорганизовать наше приложение, чтобы использовать хранилище документов iCloud вместо хранилища ключей-значений iCloud. Это означает, что мы будем использовать UIDocument
и UIDocument
его подклассы в соответствии с нашими потребностями. Кроме того, мы создадим пользовательский класс модели для нашей закладки, который будет использоваться и управляться подклассом UIDocument
.
Шаг 1. Настройка прав
В настоящее время наше приложение настроено на использование только хранилища ключей. Чтобы включить Хранение документов, нам сначала нужно настроить права нашего приложения. Откройте редактор целей , выбрав наше приложение в Project Navigator и выбрав единственную цель из списка целей. В разделе « Права » вы должны увидеть контейнеры iCloud ниже хранилища ключей-значений iCloud . Список рядом с контейнерами iCloud на данный момент пуст. Нажмите кнопку «плюс» внизу списка, и вы увидите, что Xcode создает для вас идентификатор контейнера iCloud, который совпадает с идентификатором пакета вашего приложения.
Что такое контейнер iCloud? Как следует из названия, это контейнер в хранилище данных пользователя iCloud. Указывая один (или несколько) контейнеров iCloud в файле разрешений нашего приложения, мы сообщаем операционной системе, к каким контейнерам наше приложение имеет доступ.
Шаг 2. Создание класса закладок
В предыдущем уроке мы сохранили каждую закладку как экземпляр NSDictionary
, но это не очень хорошее решение для приложения на основе документов. Вместо этого мы создадим собственный класс закладок, который позволит нам легко архивировать его данные.
Создайте новый подкласс NSObject
, выбрав File из меню, выбрав New , а затем File …. Выберите Cocoa Touch на левой панели и выберите класс Objective-C из списка шаблонов справа. Дайте классу имя Bookmark и убедитесь, что это подкласс NSObject . Укажите, где вы хотите сохранить новый класс, и нажмите « Создать» .
В заголовочный файл нашей модели мы добавляем два свойства модели закладок, которые мы использовали в предыдущем уроке: имя и URL . Оба свойства являются экземплярами NSString
. Мы также объявляем назначенный инициализатор, который принимает имя и URL-адрес в качестве параметров. Наконец, важно убедиться, что наш класс Bookmark
соответствует протоколу NSCoding
.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
#import <Foundation/Foundation.h>
@interface Bookmark : NSObject <NSCoding> {
NSString *_name;
NSString *_url;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *url;
— (id)initWithName:(NSString *)name andURL:(NSString *)url;
@end
|
В файле реализации нашей модели мы сначала определяем две константы для имени и URL нашей закладки. Это хорошая практика, поскольку она минимизирует вероятность того, что мы неправильно введем ключи, которые будем использовать в ближайшее время. Далее мы реализуем наш метод инициализации. Этот метод прост. Мы инициализируем наш экземпляр и присваиваем имя и URL-адрес нашим переменным экземпляра закладки.
Важной частью является реализация необходимых методов, чтобы привести класс Bookmark
соответствие с протоколом NSCoding
. Если протокол NSCoding
является новым для вас, тогда я NSCoding
вам прочитать Руководство по программированию архивов и сериализаций, поскольку это важная тема для любого разработчика Cocoa-Touch. Суть в том, что протокол NSCoding
позволяет нам легко архивировать и разархивировать экземпляры класса Bookmark
.
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
33
34
35
36
37
38
39
40
41
|
#import «Bookmark.h»
#define kBookmarkName @»Bookmark Name»
#define kBookmarkURL @»Bookmark URL»
@implementation Bookmark
@synthesize name = _name, url = _url;
#pragma mark —
#pragma mark Initialization
— (id)initWithName:(NSString *)name andURL:(NSString *)url {
self = [super init];
if (self) {
self.name = name;
self.url = url;
}
return self;
}
#pragma mark —
#pragma mark NSCoding Protocol
— (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.name forKey:kBookmarkName];
[coder encodeObject:self.url forKey:kBookmarkURL];
}
— (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self != nil) {
self.name = [coder decodeObjectForKey:kBookmarkName];
self.url = [coder decodeObjectForKey:kBookmarkURL];
}
return self;
}
@end
|
Шаг 3. Подкласс UIDocument
UIDocument
подклассов UIDocument
не так сложно, как вы думаете. Как я упоминал ранее, все, что нам нужно сделать, это переопределить два метода и создать свойство для модели закладок, которой он будет управлять.
Создайте новый класс, как мы сделали для нашего класса Bookmark
и присвойте ему имя BookmarkDocument
. Убедитесь, что это подкласс UIDocument
. В заголовочном файле нашего подкласса UIDocument
мы добавляем предварительное объявление для класса Bookmark
и создаем свойство для нашей модели закладок. Не забудьте синтезировать аксессоры для этого свойства.
01
02
03
04
05
06
07
08
09
10
11
|
#import <UIKit/UIKit.h>
@class Bookmark;
@interface BookmarkDocument : UIDocument {
Bookmark *_bookmark;
}
@property (nonatomic, strong) Bookmark *bookmark;
@end
|
В файле реализации мы импортируем файл заголовка класса Bookmark и определяем другую константу, которая будет служить ключом для архивирования и разархивирования модели закладок. Как я упоминал ранее, нам нужно переопределить только два метода в подклассе UIDocument : (1) loadFromContents: ofType: error: и (2) contentsForType: error:. Первый метод будет вызываться при открытии документа, тогда как второй метод будет вызываться при сохранении документа. Оба метода вызываются операционной системой. Нам никогда не нужно вызывать эти методы напрямую.
1
2
3
4
5
6
7
8
9
|
#import «BookmarkDocument.h»
#import «Bookmark.h»
#define kArchiveKey @»Bookmark»
@implementation BookmarkDocument
@synthesize bookmark = _bookmark;
|
Позвольте мне провести вас через loadFromContents: ofType: error:. Первый аргумент — это содержимое типа id
. Это может быть экземпляр NSData
или NSFileWrapper
. Последнее применимо только тогда, когда приложение использует файловые пакеты. В нашем случае мы можем ожидать экземпляр NSData
. Сначала мы проверяем, не равна ли длина экземпляра NSData
нулю. Мы инициализируем экземпляр NSKeyedUnarchiver
и предоставляем ему объект содержимого . Декодируя данные, мы получаем экземпляр класса Bookmark
. По этой причине класс Bookmark
соответствует протоколу NSCoding
. Если длина экземпляра NSData
равна нулю, мы инициализируем новую закладку со значениями по умолчанию для имени и URL. Обратите внимание, что мы возвращаем YES
в конце метода.
01
02
03
04
05
06
07
08
09
10
11
12
|
— (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
if ([contents length] > 0) {
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:contents];
self.bookmark = [unarchiver decodeObjectForKey:kArchiveKey];
[unarchiver finishDecoding];
} else {
self.bookmark = [[Bookmark alloc] initWithName:@»Bookmark Name» andURL:@»www.example.com»];
}
return YES;
}
|
ContentForType: error: метод делает обратное. То есть мы предоставляем данные, которые нужно записать на диск. Этот объект данных является так называемым снимком данных нашей модели. Мы делаем это путем инициализации экземпляра NSMutableData
и используем его для инициализации экземпляра NSKeyedArchiver
. Затем мы можем заархивировать наш экземпляр закладки, чтобы он мог быть записан на диск. Этот метод ожидает, что мы вернем экземпляр NSData
и это именно то, что мы делаем. Наш подкласс UIDocument
теперь готов к использованию.
1
2
3
4
5
6
7
8
|
— (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:self.bookmark forKey:kArchiveKey];
[archiver finishEncoding];
return data;
}
|
Шаг 4: Рефакторинг View Controllers
Если мы хотим использовать iCloud Document Storage, необходимо провести рефакторинг четырех элементов нашего приложения:
(1) загрузка, (2) отображение, (3) сохранение и (4) удаление закладок. Начнем с загрузки закладок.
Прежде чем мы взглянем на метод loadBookmarks , нам нужно объявить частное свойство, экземпляр NSMetadataQuery
. Становится понятно, почему мы должны сделать это всего за несколько минут. Не забудьте добавить два дополнительных оператора import в файл реализации нашего контроллера представления, один для класса Bookmark
и один для класса BookmarkDocument
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
#import «ViewController.h»
#import «Bookmark.h»
#import «BookmarkDocument.h»
#import «AddBookmarkViewController.h»
@interface ViewController () {
NSMetadataQuery *_query;
}
@property (nonatomic, strong) NSMetadataQuery *query;
@end
@implementation ViewController
@synthesize bookmarks = _bookmarks;
@synthesize tableView = _tableView;
@synthesize query = _query;
|
Шаг 4А: загрузка закладок
Вместо экземпляров NSDictionary
, нашего массива закладок, источник данных нашего табличного представления будет содержать экземпляры класса BookmarkDocument
. Давайте посмотрим на переработанный метод loadBookmarks . Мы начнем с инициализации массива закладок. Затем мы запрашиваем у NSFileManager
URL-адрес контейнера iCloud, который мы будем использовать для хранения наших закладок. Не поддавайтесь красочному названию этого метода. URLForUbiquityContainerIdentifier: принимает один аргумент — идентификатор контейнера iCloud, к которому мы хотим получить доступ. NSFileManager
nil в качестве аргумента, NSFileManager
автоматически выберет первый контейнер iCloud, который объявлен в файле разрешений нашего приложения. Обратите внимание, что если вы укажете идентификатор контейнера iCloud, вам также потребуется указать идентификатор группы. Правильный формат: <Идентификатор команды>. <Контейнер>.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
— (void)loadBookmarks {
if (!self.bookmarks) {
self.bookmarks = [[NSMutableArray alloc] init];
}
NSURL *baseURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (baseURL) {
self.query = [[NSMetadataQuery alloc] init];
[self.query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@»%K like ‘*'», NSMetadataItemFSNameKey];
[self.query setPredicate:predicate];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:@selector(queryDidFinish:) name:NSMetadataQueryDidFinishGatheringNotification object:self.query];
[nc addObserver:self selector:@selector(queryDidUpdate:) name:NSMetadataQueryDidUpdateNotification object:self.query];
[self.query startQuery];
}
}
|
Этот метод предназначен не только для того, чтобы узнать, где мы можем хранить наши документы iCloud. Успешно вызвав этот метод, операционная система расширит изолированную программную среду нашего приложения, включив указанную нами директорию контейнера iCloud. Это означает, что нам нужно вызвать этот метод, прежде чем мы сможем начать чтение или запись данных в этот каталог. Если вы зарегистрируете возвращенный URL на консоли, вы заметите две странности: (1) возвращенный URL для контейнера iCloud — это локальный URL (расположенный на самом устройстве), и (2) этот локальный URL не работает в песочнице нашего приложения. ICloud работает так, что мы храним документы, которые хотим сохранить в iCloud, в этом локальном каталоге, который NSFileManager
предоставляет NSFileManager
. Демон iCloud, работающий в фоновом режиме на нашем устройстве, позаботится о синхронизации документов и сделает это, даже если наше приложение не запущено.
Поскольку локальный URL-адрес находится вне изолированной программной среды нашего приложения, мы должны вызвать этот метод, прежде чем читать или записывать в этот каталог. Вызывая этот метод, мы запрашиваем разрешение у операционной системы на чтение и запись в этот каталог.
Давайте продолжим разбирать метод loadBookmarks . Мы проверяем, что URL, который мы получаем от NSFileManager
, не равен nil. Последнее подразумевает две важные вещи: (1) у нас есть местоположение, из которого мы можем читать и записывать, и (2) iCloud включен на устройстве. Второй пункт особенно важен, поскольку не на всех устройствах будет включен iCloud.
Если NSFileManager
действительно вернул действительный URL, мы инициализируем экземпляр NSMetadataQuery
и присваиваем его переменной экземпляра, которую мы объявили ранее. Класс NSMetadataQuery
позволяет нам искать документы в контейнере iCloud. После инициализации экземпляра NSMetadataQuery
мы указываем область нашего поиска. В нашем случае мы будем искать в каталоге Documents нашего контейнера iCloud, поскольку именно там мы будем хранить документы закладок. Вы можете уточнить запрос, установив предикат поиска. Если вы знакомы с основными данными, то это не ново для вас. Наш поиск будет простым, мы будем искать все документы в каталоге документов нашего контейнера iCloud, отсюда и звездочка в предикате.
Прежде чем мы начнем наш запрос, важно понять, что мы не должны ожидать немедленного результата от нашего запроса. Вместо этого мы зарегистрируем наш контроллер представления в качестве наблюдателя для уведомлений NSMetadataQueryDidUpdateNotification
и NSMetadataQueryDidFinishGatheringNotification
с нашим экземпляром запроса в качестве отправителя. Это означает, что мы будем уведомлены, когда наш запрос вернул какие-либо результаты или когда результаты были обновлены. Наконец, мы начинаем запрос.
Важно, чтобы мы сохранили ссылку на экземпляр запроса, чтобы предотвратить его освобождение. Это причина того, что наш контроллер представления сохраняет ссылку на запрос (как переменную экземпляра), пока запрос выполняется.
Давайте посмотрим на методы обратного вызова queryDidFinish: и queryDidUpdate: уведомление, чтобы увидеть, как обрабатывать результаты запроса. Оба метода передают объект отправителя уведомления, экземпляр NSMetadataQuery
, в удобный метод processQueryResults:. Когда мы посмотрим на этот метод, мы увидим, что сначала мы отключаем обновления для запроса. Это важно, так как результаты запроса могут получать живые обновления, когда происходят изменения, и мы должны предотвращать это, пока мы обрабатываем результаты запроса. Далее мы удаляем все объекты из нашего массива закладок и перечисляем результаты запроса. Каждый элемент в массиве результатов является экземпляром NSMetadataItem
, который содержит метаданные, связанные с каждым документом закладки, включая URL-адрес файла, который необходим для открытия документа. Мы запрашиваем каждый элемент метаданных для URL файла и инициализируем соответствующий документ.
Обратите внимание, что инициализация документа закладки не означает, что мы загрузили его с диска. Помните, что это делается путем отправки нашему документу закладки сообщения openWithCompletionHandler:. Если операция открытия прошла успешно и документ загружен, мы добавляем его в наш массив закладок и отображаем в виде таблицы. Наконец, нам нужно удалить наш контроллер представления в качестве наблюдателя, поскольку нам больше не нужно получать уведомления на этом этапе.
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
|
— (void)queryDidFinish:(NSNotification *)notification {
NSMetadataQuery *query = [notification object];
// Stop Updates
[query disableUpdates];
// Stop Query
[query stopQuery];
// Clear Bookmarks
[self.bookmarks removeAllObjects];
[query.results enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSURL *documentURL = [(NSMetadataItem *)obj valueForAttribute:NSMetadataItemURLKey];
BookmarkDocument *document = [[BookmarkDocument alloc] initWithFileURL:documentURL];
[document openWithCompletionHandler:^(BOOL success) {
if (success) {
[self.bookmarks addObject:document];
[self.tableView reloadData];
}
}];
}];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
|
Шаг 4B: Отображение закладок в табличном представлении
Код для отображения закладок в нашем табличном представлении не должен сильно меняться. Вместо того, чтобы извлекать правильный экземпляр NSDictionary
из источника данных, мы выбираем экземпляр класса BookmarkDocument
. Доступ к имени и URL-адресу закладки также необходимо обновить.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
— (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @»Cell Identifier»;
UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
// Fetch Bookmark
BookmarkDocument *document = [self.bookmarks objectAtIndex:indexPath.row];
// Configure Cell
cell.textLabel.text = document.bookmark.name;
cell.detailTextLabel.text = document.bookmark.url;
return cell;
}
|
Шаг 4С: Сохранение закладки
Перейдите к методу save: в AddBookmarkViewController . Вместо того, чтобы создавать NSDictionary
и отправлять его в наш основной контроллер представления, мы создаем новый экземпляр Bookmark
. Вот и все. Остальное обрабатывается в методе saveBookmark: нашего основного контроллера вида. Не забудьте добавить оператор импорта для класса Bookmark
.
1
2
3
4
5
6
7
|
— (IBAction)save:(id)sender {
Bookmark *bookmark = [[Bookmark alloc] initWithName:self.nameField.text andURL:self.urlField.text];
[self.viewController saveBookmark:bookmark];
[self dismissViewControllerAnimated:YES completion:nil];
}
|
Сохранить закладку в нашем контейнере iCloud почти так же просто, как сохранить ее в песочнице нашего приложения. Во-первых, мы спрашиваем у NSFileManager
URL нашего контейнера iCloud, как мы делали ранее. На основе этого URL мы создаем правильный URL для сохранения документа закладки в каталоге Documents контейнера iCloud. Название нашего документа может быть любым, каким мы хотим, чтобы оно было уникальным. Я решил использовать имя закладки и метку времени. Пользователь не увидит это имя файла, поэтому имя не так важно. Важно то, что он уникален.
У нас есть экземпляр закладки, но у нас еще нет документа закладки. Мы создаем новый документ закладки, инициализируя его только что созданным URL. Затем мы назначаем нашу новую закладку для свойства закладки документа. Наконец, мы добавляем документ в массив закладок и перезагружаем табличное представление.
Сохранить документ в контейнер iCloud очень просто. Мы инициируем операцию сохранения, о которой я говорил ранее, отправив нашему новому документу сообщение saveToURL: forSaveOperation: завершениеHandler:. Второй параметр этого метода указывает тип операции сохранения. В нашем случае мы передаем UIDocumentSaveForCreating
, что означает создание совершенно нового документа закладки. Поскольку в нашем примере нам не нужно делать ничего особенного, мы просто записываем сообщение на консоль, когда операция сохранения завершается.
Возможно, вы заметили, что объявление нашего метода изменилось незначительно. Мы больше не передаем экземпляр NSDictionary
в качестве единственного аргумента. Вместо этого мы передаем экземпляр класса Bookmark
. Убедитесь, что вы обновили заголовочный файл, чтобы отразить это изменение. Вам также необходимо добавить объявление класса foward, чтобы предотвратить появление предупреждений компилятора. Это задачи, с которыми вы должны быть знакомы.
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
|
— (void)saveBookmark:(Bookmark *)bookmark {
// Save Bookmark
NSURL *baseURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (baseURL) {
NSURL *documentsURL = [baseURL URLByAppendingPathComponent:@»Documents»];
NSURL *documentURL = [documentsURL URLByAppendingPathComponent:[NSString stringWithFormat:@»Bookmark_%@-%f», bookmark.name, [[NSDate date] timeIntervalSince1970]]];
BookmarkDocument *document = [[BookmarkDocument alloc] initWithFileURL:documentURL];
document.bookmark = bookmark;
// Add Bookmark To Bookmarks
[self.bookmarks addObject:document];
// Reload Table View
[self.tableView reloadData];
[document saveToURL:documentURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
if (success) {
NSLog(@»Save succeeded.»);
} else {
NSLog(@»Save failed.»);
}
}];
}
}
|
Шаг 4D: удаление закладок
Последний недостающий фрагмент головоломки — это удаление закладок. Это очень легко по сравнению с тем, что мы сделали до сих пор. Мы выбираем правильный документ закладки из источника данных и говорим NSFileManager
удалить его из контейнера iCloud, передав правильный URL. Это также удалит документ в iCloud. Вот как это просто. Конечно, мы также обновляем источник данных и представление таблицы.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
— (void)tableView:(UITableView *)aTableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Fetch Document
BookmarkDocument *document = [self.bookmarks objectAtIndex:indexPath.row];
// Delete Document
NSError *error = nil;
if (![[NSFileManager defaultManager] removeItemAtURL:document.fileURL error:&error]) {
NSLog(@»An error occurred while trying to delete document. Error %@ with user info %@.», error, error.userInfo);
}
// Update Bookmarks
[self.bookmarks removeObjectAtIndex:indexPath.row];
// Update Table View
[aTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
|
Вывод
В этом руководстве мы реорганизовали наше приложение для использования хранилища документов iCloud вместо хранилища ключей-значений iCloud. Несмотря на то, что мы переработали довольно много кода, процесс был довольно простым. Теперь у нас есть приложение на основе документов, которое намного лучше подходит для обработки больших объемов пользовательских данных. Основные компоненты на месте, и расширение приложения требует небольших усилий с нашей стороны. Обратите внимание, что наше приложение все еще является минимальной реализацией менеджера закладок. Например, нет возможности редактировать закладки. Мы также должны сделать больше проверки ошибок, если мы хотим превратить наше приложение в надежное и надежное приложение, готовое к выпуску.
Вы также могли заметить, что у нас есть ряд методов, которые нам больше не нужны. Я удалил эти методы из окончательного примера кода и предлагаю сделать то же самое. Удаление устаревшего кода также является частью процесса рефакторинга и важно, когда вы хотите, чтобы ваш код был обслуживаемым.
В следующий раз
В этом руководстве мы UIDocument
рассмотрели хранилище документов iCloud, а также класс UIDocument
. В следующем уроке мы UIManagedDocument
, отдельный подкласс UIDocument
разработанный для тесной работы с Core Data.