Статьи

Работа с iCloud: хранилище ключей-значений

Синхронизация данных приложения между устройствами является сложной и сложной задачей. К счастью, именно поэтому Apple создала iCloud. Из этой серии Tuts + Premium вы узнаете, как работает iCloud и как ваши приложения могут беспрепятственно обмениваться данными между несколькими устройствами.


  1. Работа с iCloud: Введение
  2. Работа с iCloud: хранилище ключей-значений
  3. Работа с iCloud: Хранение документов
  4. Работа с iCloud: интеграция основных данных

В первой части этой серии я представил введение в iCloud и остановился на iCloud Storage, в частности. Разработчикам доступны два типа хранилищ iCloud: хранилище ключей и хранилище документов . Хранение ключей и значений будет в центре внимания этого руководства.

Мы начнем этот урок с более детального изучения процесса настройки приложения для использования с iCloud. При правильной настройке нашего приложения мы будем использовать хранилище ключей-значений iCloud для синхронизации данных нашего приложения на нескольких устройствах.


Мы не можем проверить синхронизацию данных только с одним устройством. Для завершения этого урока вам понадобится как минимум два устройства iOS под управлением iOS 5 или выше. К сожалению, iOS Simulator нельзя использовать для тестирования iCloud Storage.

Приложение, которое мы собираемся создать, будет простым приложением для iOS, а это значит, что вы сможете запустить его на любом iPhone, iPod Touch или iPad под управлением iOS 5 или выше. Чтение этого руководства будет по-прежнему целесообразно, даже если у вас нет двух отдельных устройств iOS для тестирования. Он научит вас, как настроить iCloud и как вы можете использовать хранилище ключей-значений iCloud для улучшения ваших собственных приложений.


Я начну с процесса настройки нашего приложения для использования iCloud Storage. Однако сначала нам нужно настроить наш проект Xcode.

Создайте новый проект в XCode, выбрав шаблон приложения Single View . Назовите свое приложение Cloudy , введите идентификатор компании , установите iPhone для семейства устройств и установите флажок « Использовать автоматический подсчет ссылок» . Остальные флажки должны быть сняты. Сообщите Xcode, где вы хотите сохранить свой проект, и нажмите « Создать» .



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


Как я упоминал в первой статье этой серии Tuts + Premium, настройка приложения для использования iCloud проста и включает всего два шага. Позвольте мне шаг за шагом рассказать вам о процессе.

Откройте ваш любимый браузер и зайдите в iOS Dev Center от Apple. Войдите в свою учетную запись разработчика iOS и нажмите ссылку « Портал обеспечения iOS» справа.


Во-первых, нам нужно создать идентификатор приложения для нашего приложения. Откройте вкладку « Идентификаторы приложений » слева и нажмите кнопку « Новый идентификатор приложения» в правом верхнем углу. Присвойте своему идентификатору приложения описательное имя, чтобы его можно было легко идентифицировать позже. Затем введите идентификатор пакета, о котором я говорил несколько минут назад. Идентификатор пакета — это комбинация идентификатора вашей компании com.mobiletuts в нашем примере и названия продукта Cloudy в нашем примере. Вы можете оставить для идентификатора набора пакетов значение « Использовать Team ID» , что подходит для нашего приложения.

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


Ваш недавно созданный идентификатор приложения должен теперь присутствовать в списке идентификаторов приложений. Вы заметите, что iCloud по умолчанию отключен для каждого вновь созданного идентификатора приложения. Давайте изменим это. Справа от идентификатора приложения нажмите кнопку « Настроить» . Внизу страницы вы должны увидеть флажок Включить для iCloud . Установите флажок и нажмите Готово . Наш идентификатор приложения теперь настроен для работы с iCloud. Есть еще одна вещь, о которой нам нужно позаботиться, пока мы на Портале подготовки .



Чтобы наше приложение могло работать на наших устройствах, нам нужно создать профиль обеспечения. Все еще на портале обеспечения iOS откройте вкладку « Подготовка » слева и выберите вкладку « Разработка » вверху. Нажмите кнопку « Новый профиль» в правом верхнем углу и введите описательное имя для своего нового профиля. Затем выберите сертификат разработки, с которым вы хотите связать профиль, и выберите правильный идентификатор приложения из раскрывающегося списка. Наконец, выберите устройства, которые вы будете использовать для тестирования, из списка в нижней части страницы.


После нажатия кнопки «Отправить» в правом нижнем углу вы увидите, что ваш профиль обеспечения имеет статус « Ожидание» . После перезагрузки страницы статус профиля должен был измениться на Активный . Нажмите кнопку загрузки рядом с вашим профилем обеспечения и дважды щелкните профиль. Xcode автоматически установит профиль для вас.



Многие разработчики съеживаются, когда слышат слово « права» . Право — ничего страшного, когда вы понимаете, для чего они нужны. Права предоставляют определенные возможности или разрешения безопасности для приложения. Это все, что нужно сделать. Это простой список пар ключ-значение, указывающих операционной системе, что может и не может делать ваше приложение, и какие приложения имеют доступ к данным вашего приложения. Последнее особенно важно при работе с iCloud.

В нашем примере права iCloud позволяют нам включить iCloud Storage для нашего приложения. Чтобы настроить права нашего приложения, мы переходим к целевому редактору Xcode.

Нажмите на наш проект в Навигаторе проектов и выберите нашу цель из списка целей. Выбрав вкладку « Сводка », вы должны увидеть раздел « Права» внизу. Установите первый флажок, чтобы включить права для нашего приложения. Эта опция создаст файл полномочий для нашего приложения. Текстовое поле под флажком должно быть заполнено для вас. Теперь у нас есть возможность включить iCloud Key-Value Storage и iCloud Document Storage.


В этом руководстве я буду говорить только о хранилище ключей-значений iCloud, поэтому установите флажок рядом с хранилищем ключей-iCloud. Опять же, Xcode предварительно заполняет текстовое поле рядом с флажком идентификатором пакета приложения (без идентификатора начального числа пакета). Убедитесь, что этот идентификатор пакета соответствует идентификатору, который вы указали на портале обеспечения при создании своего идентификатора приложения. Я расскажу о контейнерах iCloud в следующем уроке, поскольку они связаны с iCloud Document Storage.

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

Прежде чем мы запачкаем руки API-интерфейсом iCloud, нам нужно настроить параметры сборки. Все еще в целевом редакторе Xcode, выберите вкладку Build Settings и прокрутите вниз до раздела Signing Code . Установите идентификатор подписи кода для конфигураций Debug и Release так, чтобы они соответствовали профилю, который мы только что создали на Портале подготовки iOS .



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

Приложение, которое мы собираемся создать, будет простым диспетчером закладок. Мы можем добавлять и удалять закладки, и наши закладки будут синхронизироваться с нашими устройствами. Как это круто? Давайте начнем.

Откройте файл заголовка нашего контроллера представления и создайте переменную экземпляра (ivar) типа NSMutableArray с именем закладок . IVAR будет хранить наши закладки. Закладки будут отображаться в виде таблицы. Создайте выход для табличного представления в заголовочном файле нашего контроллера представления и убедитесь, что наш контроллер представления соответствует источнику данных табличного представления и делегирует протоколы. Кроме того, не забудьте синтезировать средства доступа для обоих свойств в файле реализации нашего контроллера представления. Затем добавьте два действия в ViewController.h , отредактируйте Bookmarks: и addBookmark:. Готов? Перейдите к файлу ViewController.xib, чтобы все подключить .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
#import <UIKit/UIKit.h>
  
@interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate> {
    NSMutableArray *_bookmarks;
      
    __weak UITableView *_tableView;
}
  
@property (nonatomic, strong) NSMutableArray *bookmarks;
  
@property (nonatomic, weak) IBOutlet UITableView *tableView;
  
— (IBAction)editBookmarks:(id)sender;
— (IBAction)addBookmark:(id)sender;
  
@end

Начните с перетаскивания навигационной панели к представлению нашего контроллера представления и расположите ее в самом верху. Перетащите экземпляр UIBarButtonItem слева от панели навигации и установите его идентификатор в Инспекторе атрибутов на « Редактировать» . Управляйте перетаскиванием с кнопки редактирования на владельца нашего файла и выберите метод editBookmarks: метод. Перетащите второй элемент кнопки панели справа от панели навигации и присвойте ему идентификатор « Добавить» . Управляйте перетаскиванием с кнопки «Добавить» на владельца нашего файла и выберите метод addBookmark : . Наконец, перетащите экземпляр UITableView в представление нашего контроллера представления и подключите его источник данных и делегируйте выходы объекту- владельцу файла . Выполнено.


Реализация протоколов нашего источника данных и протоколов делегатов проста и понятна. В методе numberOfSectionsInTableView: мы проверяем, не равен ли наш источник данных, то есть наши закладки, нулю. Если это ноль, мы возвращаем 1. Если нет, мы возвращаем 0. TableView: numberOfRowsInSection: метод почти идентичен. Вместо этого, если наш источник данных не ноль, мы возвращаем количество элементов, которые он содержит. Если наш источник данных ноль, мы возвращаем 0.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
— (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    if (self.bookmarks) {
        return 1;
    } else {
        return 0;
    }
}
  
— (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (!self.bookmarks) {
        return 0;
    } else {
        return self.bookmarks.count;
    }
}

В tableView: cellForRowAtIndexPath: метод мы начинаем с объявления статического идентификатора ячейки с целью повторного использования ячейки, а затем запрашиваем в табличном представлении ячейку многократного использования с идентификатором ячейки, который мы только что объявили. Если повторно используемая ячейка не была возвращена, мы инициализируем новую ячейку в стиле UITableViewCellStyleSubtitle и передаем наш идентификатор ячейки для повторного использования ячейки. Далее мы выбираем правильную закладку из источника данных. Каждая закладка будет словарём с двумя парами ключ-значение, именем и URL . Мы настраиваем ячейку, устанавливая ее метку и метку сведений с именем закладки и 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
    NSDictionary *bookmark = [self.bookmarks objectAtIndex:indexPath.row];
      
    // Configure Cell
    cell.textLabel.text = [bookmark objectForKey:@»name»];
    cell.detailTextLabel.text = [bookmark objectForKey:@»url»];
      
    return cell;
}

Пользователь также имеет возможность редактировать свои закладки. В tableView: canEditRowAtIndexPath: метод мы возвращаем YES . TableView: commitEditingStyle: forRowAtIndexPath: немного сложнее. Начнем с проверки, что стиль редактирования ячейки равен UITableViewCellEditingStyleDelete , то есть закладка была удалена. Сначала мы обновляем наш источник данных, удаляя закладку из массива закладок. Затем мы сохраняем источник данных, отправляя нашему контроллеру представления сообщение saveBookmarks и, наконец, обновляем таблицу, чтобы отразить изменения, которые мы внесли в источник данных.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
— (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
    return YES;
}
  
— (void)tableView:(UITableView *)aTableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // Update Bookmarks
        [self.bookmarks removeObjectAtIndex:indexPath.row];
          
        // Save Bookmarks
        [self saveBookmarks];
  
        // Update Table View
        [aTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
    }
}

Давайте посмотрим на наши действия сейчас. EditBookmarks: метод не может быть проще. Мы переключаем состояние редактирования табличного представления анимированным способом.

1
2
3
— (IBAction)editBookmarks:(id)sender {
    [self.tableView setEditing:!self.tableView.editing animated:YES];
}

Метод addBookmark: требует немного больше работы. Мы инициализируем новый экземпляр AddBookmarkViewController , контроллер представления, который мы вскоре создадим, и устанавливаем его свойство контроллера представления в self . Затем мы представляем недавно созданный контроллер представления модально. Как следует из названия, AddBookmarkViewController отвечает за создание новых закладок. Давайте внимательнее посмотрим на AddBookmarkViewController .

1
2
3
4
5
6
7
8
— (IBAction)addBookmark:(id)sender {
    // Initialize Add Bookmark View Controller
    AddBookmarkViewController *vc = [[AddBookmarkViewController alloc] initWithNibName:@»AddBookmarkViewController» bundle:[NSBundle mainBundle]];
    vc.viewController = self;
      
    // Present View Controller Modally
    [self presentViewController:vc animated:YES completion:nil];
}

Реализация AddBookmarkViewController очень проста, поэтому я не буду вдаваться в подробности. Контроллер вида имеет слабую ссылку на наш основной контроллер вида. Это позволяет ему уведомлять наш главный контроллер представления о создании новой закладки. Он также имеет два выхода типа UITextField , который позволяет пользователю вводить имя и URL-адрес для каждой закладки.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
#import <UIKit/UIKit.h>
  
@class ViewController;
  
@interface AddBookmarkViewController : UIViewController {
    __weak ViewController *_viewController;
      
    __weak UITextField *_nameField;
    __weak UITextField *_urlField;
}
  
@property (nonatomic, weak) ViewController *viewController;
  
@property (nonatomic, weak) IBOutlet UITextField *nameField;
@property (nonatomic, weak) IBOutlet UITextField *urlField;
  
@end

Файл XIB содержит панель навигации вверху с кнопкой отмены и кнопкой сохранения . Само представление содержит текстовые поля имени и URL, которые я только что упомянул. Кнопки отмены и сохранения связаны с действием отмены: и сохранения: соответственно. Действие cancel: просто отклоняет наш контроллер модального представления. Действие save: так же просто. Мы создаем новую закладку (т.е. экземпляр NSDictionary ) с данными, введенными пользователем в текстовые поля, и сообщаем нашему главному контроллеру представления о новой закладке. Важно отметить, что это не лучшее решение для того, чтобы AddBookmarkViewController нашему главному контроллеру представления, когда наша AddBookmarkViewController создала новую закладку. Этот подход вводит тесную связь, которой следует избегать, насколько это возможно. Лучшим подходом было бы принять модель делегирования.


01
02
03
04
05
06
07
08
09
10
11
12
13
14
— (IBAction)cancel:(id)sender {
    [self dismissViewControllerAnimated:YES completion:nil];
}
  
— (IBAction)save:(id)sender {
    NSString *name = self.nameField.text;
    NSString *url = self.urlField.text;
      
    NSDictionary *bookmark = [[NSDictionary alloc] initWithObjectsAndKeys:name, @»name», url, @»url», nil];
      
    [self.viewController saveBookmark:bookmark];
      
    [self dismissViewControllerAnimated:YES completion:nil];
}

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

01
02
03
04
05
06
07
08
09
10
— (void)saveBookmark:(NSDictionary *)bookmark {
    // Add Bookmark To Bookmarks
    [self.bookmarks addObject:bookmark];
      
    // Save Bookmarks
    [self saveBookmarks];
      
    // Reload Table View
    [self.tableView reloadData];
}

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

В нашем методе viewDidLoad: мы отправляем нашему контроллеру представления сообщение loadBookmarks . Давайте посмотрим на метод loadBookmarks . Мы будем хранить закладки пользователя в базе данных по умолчанию . Сначала мы проверяем, есть ли уже запись в базе данных по умолчанию для пользователя с ключевыми bookmarks . Если это так, мы инициализируем наши закладки содержимым хранимого объекта. Если это не так, мы инициализируем наши закладки пустым изменяемым массивом и сохраняем этот массив в базе данных пользователя по умолчанию. Просто. Правильно?

1
2
3
4
5
6
— (void)viewDidLoad {
    [super viewDidLoad];
      
    // Load Bookmarks
    [self loadBookmarks];
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
— (void)loadBookmarks {
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
      
    if ([ud objectForKey:@»bookmarks»] != nil) {
        // Load Local Copy
        self.bookmarks = [NSMutableArray arrayWithArray:[ud objectForKey:@»bookmarks»]];
          
    } else {
        // Initialize Bookmarks
        self.bookmarks = [[NSMutableArray alloc] init];
          
        // Save Local Copy
        [ud setObject:self.bookmarks forKey:@»bookmarks»];
    }
}

Сохранение закладок еще проще. Мы сохраняем наш объект закладок в базе данных пользователя по умолчанию, используя ключевые bookmarks . Это все, что нужно сделать.

1
2
3
4
— (void)saveBookmarks {
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    [ud setObject:self.bookmarks forKey:@»bookmarks»];
}

Это было довольно много работы. Теперь вы можете создавать и запускать ваше приложение. Вы должны иметь возможность добавлять закладки и удалять закладки. Закладки сохраняются на диск, поэтому вы не потеряете данные при выходе из приложения.


Чтобы сделать наше приложение действительно блестящим, мы будем использовать iCloud для синхронизации наших закладок на наших устройствах. Это удивит вас, насколько это легко. Как я упоминал в предыдущем руководстве этой серии, хранилище ключей-значений iCloud работает во многом как NSUserDefaults , то есть оно хранит пары ключ-значение. В терминологии iCloud хранилище называется NSUbiquitousKeyValueStore . Начнем с сохранения наших закладок не только локально, но и в iCloud.

Перейдите к нашему saveBookmarks: метод и измените наш метод так, как показано ниже. Мы начинаем с извлечения ссылки на хранилище по умолчанию, вызывая defaultStore NSUbiquitousKeyValueStore класса NSUbiquitousKeyValueStore . Опять же, это очень похоже на NSUserDefaults . Если наша ссылка на хранилище не равна нулю, мы сохраняем наш объект закладок в хранилище с тем же ключом, который мы использовали для хранения наших закладок в базе данных пользователя по умолчанию. Наконец, мы синхронизируем магазин.

01
02
03
04
05
06
07
08
09
10
11
12
13
— (void)saveBookmarks {
    // Save Local Copy
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    [ud setObject:self.bookmarks forKey:@»bookmarks»];
      
    // Save To iCloud
    NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
      
    if (store != nil) {
        [store setObject:self.bookmarks forKey:@»bookmarks»];
        [store synchronize];
    }
}

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

Кроме того, даже несмотря на то, что мы вызвали синхронизированный метод в экземпляре хранилища, в большинстве случаев это не требуется. Операционная система сделает это за вас в соответствующее время. Знаете ли вы, что NSUserDefaults имеет метод синхронизации, который работает почти одинаково?

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

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

Посмотрим, как это работает. В нашем методе viewDidLoad мы делаем четыре вещи. Сначала мы получаем ссылку на хранилище ключей. Во-вторых, мы добавляем наш контроллер представления в качестве наблюдателя для любых уведомлений с именем NSUbiquitousKeyValueStoreDidChangeExternallyNotification отправляемых хранилищем значений ключей. Когда мы получим такое уведомление, мы обработаем это уведомление в нашем методе updateKeyValuePairs: (который мы напишем через минуту). Далее мы отправляем в магазин сообщение о синхронизации . Наконец, мы загружаем наши закладки, как и раньше.

01
02
03
04
05
06
07
08
09
10
11
12
— (void)viewDidLoad {
    [super viewDidLoad];
      
    NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateKeyValuePairs:) name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:store];
      
    // Synchronize Store
    [store synchronize];
      
    // Load Bookmarks
    [self loadBookmarks];
}

Нам остается только реализовать метод updateKeyValuePairs : . Давайте сделаем это прямо сейчас. Словарь userInfo уведомления содержит две важные пары ключ-значение: (1) причину отправки уведомления и (2) ключи в хранилище значений ключей, значения которых были изменены. Начнем с того, что внимательно рассмотрим причину уведомления. Сначала мы проверяем, указана ли причина, и возвращаемся, если не было указано ни одной причины Если причина действительно была указана, мы продолжаем, только если причиной было изменение на сервере ( NSUbiquitousKeyValueStoreServerChange ) или локальные изменения были NSUbiquitousKeyValueStoreServerChange из-за начальной синхронизации с сервером ( NSUbiquitousKeyValueStoreInitialSyncChange ). Если какой-либо из этих критериев удовлетворяется, мы извлекаем массив ключей, который изменился, и перебираем ключи, чтобы посмотреть, есть ли среди них ключ закладок. Если это так, мы берем значение, связанное с ключом, и обновляем наш источник данных, а также базу данных пользователя по умолчанию с этим значением. Наконец, мы перезагружаем табличное представление, чтобы отразить изменения.

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
— (void)updateKeyValuePairs:(NSNotification *)notification {
    NSDictionary *userInfo = [notification userInfo];
    NSNumber *changeReason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
    NSInteger reason = -1;
      
    // Is a Reason Specified?
    if (!changeReason) {
        return;
          
    } else {
        reason = [changeReason integerValue];
    }
      
    // Proceed If Reason Was (1) Changes on Server or (2) Initial Sync
    if ((reason == NSUbiquitousKeyValueStoreServerChange) || (reason == NSUbiquitousKeyValueStoreInitialSyncChange)) {
        NSArray *changedKeys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
        NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
          
        // Search Keys for «bookmarks» Key
        for (NSString *key in changedKeys) {
            if ([key isEqualToString:@»bookmarks»]) {
                // Update Data Source
                self.bookmarks = [NSMutableArray arrayWithArray:[store objectForKey:key]];
                  
                // Save Local Copy
                NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
                [ud setObject:self.bookmarks forKey:@»bookmarks»];
                  
                // Reload Table View
                [self.tableView reloadData];
            }
        }
    }
}

Есть два момента, которые требуют особого внимания. Во-первых, как мы видели в реализации updateKeyValuePairs:, мы получаем массив, содержащий все ключи в хранилище значений ключей со значениями, которые были изменены. Это оптимизация для предотвращения отправки iCloud уведомления для каждой измененной пары ключ-значение. Во-вторых, настоятельно рекомендуется хранить любые данные из хранилища ключей-значений локально. Пользовательская база данных по умолчанию хорошо подходит для этой цели, но вы можете выбрать любое решение, соответствующее вашим потребностям. Причина хранения локальной копии — не полагаться на iCloud для хранения данных. Не каждый пользователь будет иметь учетную запись iCloud или iCloud включен на своем устройстве.

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

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


Прежде чем закончить этот урок, я хочу еще раз рассказать о плюсах и минусах iCloud Key-Value Storage, поскольку их важно помнить. Плюсы к настоящему времени очевидны, их легко принять, быстро внедрить, и это очень похоже на работу NSUserDefaults . Однако также важно помнить о минусах. Пары ключ-значение — это то, что они есть. То есть простые пары без какой-либо формы обработки конфликтов, если вы не реализуете свою собственную логику обработки конфликтов. Последнее не рекомендуется, поскольку хранилище ключей не было построено с учетом этого. Хранение документов обеспечивает встроенную поддержку конфликтов и поэтому является гораздо лучшим вариантом, если вам требуется обработка конфликтов. Наконец, не забывайте, что хранилище ключей-значений может хранить только 1 МБ данных с максимум 1024 парами ключ-значение. Несмотря на то, что мы использовали хранилище ключей в этом приложении для иллюстрации, мы не можем гарантировать, что никогда не будет пользователя со списком закладок, превышающим предел в 1 МБ.

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


В этом уроке мы узнали, как настроить приложение для использования с Apple iCloud. Мы также более подробно рассмотрели iCloud Key-Value Storage. В следующем уроке мы сосредоточимся на другом типе хранилища iCloud: хранилище документов. Этот тип хранилища iCloud предназначен для приложений с большими объемами данных, и это именно то, что мы создадим в следующем уроке.