Использование iCloud с Xamarin.iOS

API хранилища iCloud в iOS 5 позволяет приложениям сохранять пользовательские документы и данные, относящиеся к приложениям, в централизованном расположении и получать доступ к этим элементам со всех устройств пользователя.

Доступно четыре типа хранилища:

  • Хранилище key-Value — для совместного использования небольших объемов данных с приложением на других устройствах пользователя.

  • Хранилище UIDocument — для хранения документов и других данных в учетной записи iCloud пользователя с помощью подкласса UIDocument.

  • CoreData — хранилище базы данных SQLite.

  • Отдельные файлы и каталоги — для управления большим количеством различных файлов непосредственно в файловой системе.

В этом документе рассматриваются первые два типа — пары "Ключ-значение" и подклассы UIDocument, а также способы использования этих функций в Xamarin.iOS.

Внимание

Компания Apple предоставляет инструменты, которые помогают разработчикам надлежащим образом соблюдать Общий регламент по защите данных Европейского союза (GDPR).

Требования

  • Последняя стабильная версия Xamarin.iOS
  • Xcode 10
  • Visual Studio для Mac или Visual Studio 2019.

Подготовка к разработке iCloud

Приложения должны быть настроены для использования iCloud как на портале подготовки Apple, так и в самом проекте. Прежде чем разрабатывать для iCloud (или попробовать примеры), выполните указанные ниже действия.

Чтобы правильно настроить приложение для доступа к iCloud:

  • Найдите teamID — войдите в developer.apple.com и посетите > сводку учетной записи разработчика учетной записи > участника, чтобы получить идентификатор команды (или отдельный идентификатор для отдельных разработчиков). Это будет 10 символьная строка ( например, A93A5CM278 ) — это часть "идентификатора контейнера".

  • Создайте идентификатор приложения. Чтобы создать идентификатор приложения, выполните действия, описанные в разделе "Подготовка для технологий Магазина" руководства по подготовке устройств и обязательно проверка iCloud в качестве разрешенной службы:

Check iCloud as an allowed service

  • Создайте новый профиль подготовки. Чтобы создать профиль подготовки, выполните действия, описанные в руководстве по подготовке устройств.

  • Добавьте идентификатор контейнера в entitlements.plist — формат TeamID.BundleIDидентификатора контейнера. Дополнительные сведения см. в руководстве по работе с правами .

  • Настройте свойства проекта. В файле Info.plist убедитесь, что идентификатор пакета соответствует набору идентификаторов пакета при создании идентификатора приложения; Подписывание пакета iOS использует профиль подготовки, содержащий идентификатор приложения с Служба приложений iCloud, и выбранный файл настраиваемых прав. Все это можно сделать в Visual Studio в области свойств проекта.

  • Включите iCloud на устройстве— перейдите к Параметры > iCloud и убедитесь, что устройство вошедшего в систему. Выберите и включите параметр "Документы и данные ".

  • Необходимо использовать устройство для тестирования iCloud — он не будет работать на симуляторе. На самом деле, вам действительно нужны два или более устройств, вошедшего в систему с одинаковым идентификатором Apple ID, чтобы увидеть iCloud в действии.

Служба хранилища "ключ-значение"

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

Существуют некоторые ограничения, которые следует учитывать при использовании хранилища key-value:

  • Максимальный размер ключа— имена ключей не могут превышать 64 байта.

  • Максимальный размер значения — не удается сохранить более 64 килобайт в одном значении.

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

  • Типы данных — можно хранить только основные типы, такие как строки, числа и логические элементы.

В примере iCloudKeyValue показано, как это работает. Пример кода создает ключ с именем для каждого устройства: этот ключ можно задать на одном устройстве и посмотреть, как значение распространяется другим пользователям. Он также создает ключ с именем "Общий", который можно изменить на любом устройстве - если вы редактируете на многих устройствах одновременно, iCloud решит, какое значение "wins" (используя метку времени при изменении) и будет распространяться.

На этом снимке экрана показан пример использования. При получении уведомлений об изменениях из iCloud они печатаются в режиме прокрутки текста в нижней части экрана и обновляются в полях ввода.

The flow of messages between devices

Настройка и получение данных

В этом коде показано, как задать строковое значение.

var store = NSUbiquitousKeyValueStore.DefaultStore;
store.SetString("testkey", "VALUE IN THE CLOUD");  // key and value
store.Synchronize();

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

Вы можете получить значение с помощью этого кода:

var store = NSUbiquitousKeyValueStore.DefaultStore;
display.Text = store.GetString("testkey");

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

Удаление данных

Чтобы полностью удалить пару "ключ-значение", используйте метод Remove следующим образом:

var store = NSUbiquitousKeyValueStore.DefaultStore;
store.Remove("testkey");
store.Synchronize();

Наблюдение за изменениями

Приложение также может получать уведомления при изменении значений в iCloud, добавив в него NSNotificationCenter.DefaultCenterнаблюдателя. Следующий код из метода KeyValueViewController.csViewWillAppear показывает, как прослушивать эти уведомления и создавать список ключей, которые были изменены:

keyValueNotification =
NSNotificationCenter.DefaultCenter.AddObserver (
    NSUbiquitousKeyValueStore.DidChangeExternallyNotification, notification => {
    Console.WriteLine ("Cloud notification received");
    NSDictionary userInfo = notification.UserInfo;

    var reasonNumber = (NSNumber)userInfo.ObjectForKey (NSUbiquitousKeyValueStore.ChangeReasonKey);
    nint reason = reasonNumber.NIntValue;

    var changedKeys = (NSArray)userInfo.ObjectForKey (NSUbiquitousKeyValueStore.ChangedKeysKey);
    var changedKeysList = new List<string> ();
    for (uint i = 0; i < changedKeys.Count; i++) {
        var key = changedKeys.GetItem<NSString> (i); // resolve key to a string
        changedKeysList.Add (key);
    }
    // now do something with the list...
});

Затем код может предпринять некоторые действия со списком измененных ключей, например обновление локальной копии этих ключей или обновление пользовательского интерфейса с новыми значениями.

Возможные причины изменений: ServerChange (0), InitialSyncChange (1) или QuotaViolationChange (2). Вы можете получить доступ к причине и выполнить другую обработку при необходимости (например, может потребоваться удалить некоторые ключи в результате QuotaViolationChange).

Служба хранилища документа

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

На этой схеме показано, как все это подходит вместе. Каждое устройство имеет данные, сохраненные в локальном хранилище (UbiquityContainer), и управляющая программа iCloud операционной системы заботится о отправке и получении данных в облаке. Чтобы предотвратить одновременный доступ к файлу ubiquityContainer, необходимо выполнить все доступ к файлу с помощью FilePresenter/FileCoordinator. Класс реализует их. В UIDocument этом примере показано, как использовать UIDocument.

The document storage overview

Пример iCloudUIDoc реализует простой UIDocument подкласс, содержащий одно текстовое поле. Текст отрисовывается в виде UITextView и редактируется iCloud на других устройствах с сообщением уведомления, отображаемым красным цветом. Пример кода не имеет более сложных функций iCloud, таких как разрешение конфликтов.

На этом снимке экрана показан пример приложения— после изменения текста и нажатия клавиши UpdateChangeCount документ синхронизируется с помощью iCloud с другими устройствами.

This screenshot shows the sample application after changing the text and pressing UpdateChangeCount

В примере iCloudUIDoc существует пять частей:

  1. Доступ к UbiquityContainer — определите, включена ли iCloud, и если это так, путь к области хранилища iCloud приложения.

  2. Создание подкласса UIDocument — создание класса промежуточного между хранилищем iCloud и объектами модели.

  3. Поиск и открытие документов iCloud — использование NSFileManager и NSPredicate поиск документов iCloud и их открытие.

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

  5. Сохранение документов iCloud — убедитесь, что изменения, внесенные в пользовательский интерфейс, сохраняются на диске и iCloud.

Все операции iCloud выполняются (или должны выполняться) асинхронно, чтобы они не блокируются во время ожидания того, что произойдет что-то. В примере вы увидите три различных способа выполнения:

Потоки — в AppDelegate.FinishedLaunching первоначальном вызове GetUrlForUbiquityContainer выполняется в другом потоке, чтобы предотвратить блокировку основного потока.

NotificationCenter — регистрация уведомлений при выполнении асинхронных операций NSMetadataQuery.StartQuery .

Обработчики завершения — передача методов для выполнения при выполнении асинхронных операций, таких как UIDocument.Open.

Доступ к UbiquityContainer

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

Этот код находится в методе AppDelegate.FinishedLaunching примера.

// GetUrlForUbiquityContainer is blocking, Apple recommends background thread or your UI will freeze
ThreadPool.QueueUserWorkItem (_ => {
    CheckingForiCloud = true;
    Console.WriteLine ("Checking for iCloud");
    var uburl = NSFileManager.DefaultManager.GetUrlForUbiquityContainer (null);
    // OR instead of null you can specify "TEAMID.com.your-company.ApplicationName"

    if (uburl == null) {
        HasiCloud = false;
        Console.WriteLine ("Can't find iCloud container, check your provisioning profile and entitlements");

        InvokeOnMainThread (() => {
            var alertController = UIAlertController.Create ("No \uE049 available",
            "Check your Entitlements.plist, BundleId, TeamId and Provisioning Profile!", UIAlertControllerStyle.Alert);
            alertController.AddAction (UIAlertAction.Create ("OK", UIAlertActionStyle.Destructive, null));
            viewController.PresentViewController (alertController, false, null);
        });
    } else { // iCloud enabled, store the NSURL for later use
        HasiCloud = true;
        iCloudUrl = uburl;
        Console.WriteLine ("yyy Yes iCloud! {0}", uburl.AbsoluteUrl);
    }
    CheckingForiCloud = false;
});

Хотя пример не делает это, Apple рекомендует вызывать GetUrlForUbiquityContainer всякий раз, когда приложение приходит на передний план.

Создание подкласса UIDocument

Все файлы и каталоги iCloud (т. е. все, хранящиеся в каталоге UbiquityContainer), должны управляться с помощью методов NSFileManager, реализации протокола NSFilePresenter и записи с помощью NSFileCoordinator. Самый простой способ сделать все это не для того, чтобы написать его самостоятельно, а подкласс UIDocument, который делает все это для вас.

Существует только два метода, которые необходимо реализовать в подклассе UIDocument для работы с iCloud:

  • LoadFromContents — передает NSData содержимого файла для распаковки в класс модели или es.

  • ContentsForType — запрос на предоставление представления NSData класса модели/es для сохранения на диске (и облака).

В этом примере кода из iCloudUIDoc\MonkeyDocument.cs показано, как реализовать UIDocument.

public class MonkeyDocument : UIDocument
{
    // the 'model', just a chunk of text in this case; must easily convert to NSData
    NSString dataModel;
    // model is wrapped in a nice .NET-friendly property
    public string DocumentString {
        get {
            return dataModel.ToString ();
        }
        set {
            dataModel = new NSString (value);
        }
    }
    public MonkeyDocument (NSUrl url) : base (url)
    {
        DocumentString = "(default text)";
    }
    // contents supplied by iCloud to display, update local model and display (via notification)
    public override bool LoadFromContents (NSObject contents, string typeName, out NSError outError)
    {
        outError = null;

        Console.WriteLine ("LoadFromContents({0})", typeName);

        if (contents != null)
            dataModel = NSString.FromData ((NSData)contents, NSStringEncoding.UTF8);

        // LoadFromContents called when an update occurs
        NSNotificationCenter.DefaultCenter.PostNotificationName ("monkeyDocumentModified", this);
        return true;
    }
    // return contents for iCloud to save (from the local model)
    public override NSObject ContentsForType (string typeName, out NSError outError)
    {
        outError = null;

        Console.WriteLine ("ContentsForType({0})", typeName);
        Console.WriteLine ("DocumentText:{0}",dataModel);

        NSData docData = dataModel.Encode (NSStringEncoding.UTF8);
        return docData;
    }
}

Модель данных в этом случае очень простая — одно текстовое поле. Модель данных может быть сложной по мере необходимости, например xml-документ или двоичные данные. Основная роль реализации UIDocument заключается в переводе между классами модели и представлением NSData, которое можно сохранить или загрузить на диск.

Поиск и открытие документов iCloud

Пример приложения имеет дело только с одним файлом — test.txt— поэтому код в AppDelegate.cs создает NSPredicate и NSMetadataQuery ищет специально для этого имени файла. Выполняется NSMetadataQuery асинхронно и отправляет уведомление после завершения. DidFinishGathering получает вызов наблюдателя уведомлений, останавливает запрос и вызывает LoadDocument, который использует UIDocument.Open метод с обработчиком завершения для попытки загрузить файл и отобразить его в MonkeyDocumentViewController.

string monkeyDocFilename = "test.txt";
void FindDocument ()
{
    Console.WriteLine ("FindDocument");
    query = new NSMetadataQuery {
        SearchScopes = new NSObject [] { NSMetadataQuery.UbiquitousDocumentsScope }
    };

    var pred = NSPredicate.FromFormat ("%K == %@", new NSObject[] {
        NSMetadataQuery.ItemFSNameKey, new NSString (MonkeyDocFilename)
    });

    Console.WriteLine ("Predicate:{0}", pred.PredicateFormat);
    query.Predicate = pred;

    NSNotificationCenter.DefaultCenter.AddObserver (
        this,
        new Selector ("queryDidFinishGathering:"),
        NSMetadataQuery.DidFinishGatheringNotification,
        query
    );

    query.StartQuery ();
}

[Export ("queryDidFinishGathering:")]
void DidFinishGathering (NSNotification notification)
{
    Console.WriteLine ("DidFinishGathering");
    var metadataQuery = (NSMetadataQuery)notification.Object;
    metadataQuery.DisableUpdates ();
    metadataQuery.StopQuery ();

    NSNotificationCenter.DefaultCenter.RemoveObserver (this, NSMetadataQuery.DidFinishGatheringNotification, metadataQuery);
    LoadDocument (metadataQuery);
}

void LoadDocument (NSMetadataQuery metadataQuery)
{
    Console.WriteLine ("LoadDocument");

    if (metadataQuery.ResultCount == 1) {
        var item = (NSMetadataItem)metadataQuery.ResultAtIndex (0);
        var url = (NSUrl)item.ValueForAttribute (NSMetadataQuery.ItemURLKey);
        doc = new MonkeyDocument (url);

        doc.Open (success => {
            if (success) {
                Console.WriteLine ("iCloud document opened");
                Console.WriteLine (" -- {0}", doc.DocumentString);
                viewController.DisplayDocument (doc);
            } else {
                Console.WriteLine ("failed to open iCloud document");
            }
        });
    } // TODO: if no document, we need to create one
}

Отображение документов iCloud

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

В примере iCloudUIDoc\MonkeyDocumentViewController.cs отображается текст MonkeyDocument в объекте UITextView. ViewDidLoad прослушивает уведомление, отправленное в методе MonkeyDocument.LoadFromContents . LoadFromContents вызывается, когда iCloud содержит новые данные для файла, поэтому уведомление указывает на то, что документ был обновлен.

NSNotificationCenter.DefaultCenter.AddObserver (this,
    new Selector ("dataReloaded:"),
    new NSString ("monkeyDocumentModified"),
    null
);

Пример обработчика уведомлений кода вызывает метод обновления пользовательского интерфейса. В этом случае без обнаружения конфликтов или разрешения конфликтов.

[Export ("dataReloaded:")]
void DataReloaded (NSNotification notification)
{
    doc = (MonkeyDocument)notification.Object;
    // we just overwrite whatever was being typed, no conflict resolution for now
    docText.Text = doc.DocumentString;
}

Сохранение документов iCloud

Чтобы добавить UIDocument в iCloud, можно вызвать UIDocument.Save напрямую (только для новых документов) или переместить существующий файл с помощью NSFileManager.DefaultManager.SetUbiquitious. Пример кода создает новый документ непосредственно в контейнере ubiquity с этим кодом (здесь есть два обработчика завершения, один для операции и другой для Save open):

var docsFolder = Path.Combine (iCloudUrl.Path, "Documents"); // NOTE: Documents folder is user-accessible in Settings
var docPath = Path.Combine (docsFolder, MonkeyDocFilename);
var ubiq = new NSUrl (docPath, false);
var monkeyDoc = new MonkeyDocument (ubiq);
monkeyDoc.Save (monkeyDoc.FileUrl, UIDocumentSaveOperation.ForCreating, saveSuccess => {
Console.WriteLine ("Save completion:" + saveSuccess);
if (saveSuccess) {
    monkeyDoc.Open (openSuccess => {
        Console.WriteLine ("Open completion:" + openSuccess);
        if (openSuccess) {
            Console.WriteLine ("new document for iCloud");
            Console.WriteLine (" == " + monkeyDoc.DocumentString);
            viewController.DisplayDocument (monkeyDoc);
        } else {
            Console.WriteLine ("couldn't open");
        }
    });
} else {
    Console.WriteLine ("couldn't save");
}

Последующие изменения документа не сохраняются напрямую. Вместо этого мы сообщаем UIDocument , что он изменился с UpdateChangeCount, и он автоматически запланируют сохранение на диск:

doc.UpdateChangeCount (UIDocumentChangeKind.Done);

Управление документами iCloud

Пользователи могут управлять документами iCloud в каталоге "Документы" контейнера ubiquity за пределами приложения с помощью Параметры; они могут просматривать список файлов и проводите пальцем, чтобы удалить. Код приложения должен иметь возможность обрабатывать ситуацию, когда документы удаляются пользователем. Не сохраняйте данные внутреннего приложения в каталоге "Документы ".

Managing iCloud Documents workflow

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

Screenshot shows a warning for Document Updates Pending.

Screenshot shows a warning for Delete i Cloud.

Резервное копирование iCloud

При резервном копировании в iCloud не является функцией, которая напрямую доступна разработчиками, способ разработки приложения может повлиять на взаимодействие с пользователем. Apple предоставляет рекомендации по служба хранилища данных iOS для разработчиков, которые будут следовать в своих приложениях iOS.

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

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

Итоги

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

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

Наконец, она включала краткое обсуждение того, как добавление iCloud Backup должно повлиять на дизайн приложения.