Xamarin.iOS에서 iCloud 사용

iOS 5의 iCloud Storage API를 사용하면 애플리케이션이 사용자 문서 및 애플리케이션 관련 데이터를 중앙 위치에 저장하고 모든 사용자의 디바이스에서 해당 항목에 액세스할 수 있습니다.

다음과 같은 네 가지 유형의 스토리지를 사용할 수 있습니다.

  • 키-값 스토리지 - 사용자의 다른 디바이스에서 애플리케이션과 소량의 데이터를 공유합니다.

  • UIDocument 스토리지 - UIDocument의 하위 클래스를 사용하여 사용자의 iCloud 계정에 문서 및 기타 데이터를 저장합니다.

  • CoreData - SQLite 데이터베이스 스토리지.

  • 개별 파일 및 디렉터리 - 파일 시스템에서 직접 다양한 파일을 관리하기 위한 것입니다.

이 문서에서는 처음 두 가지 유형인 키-값 쌍 및 UIDocument 하위 클래스와 Xamarin.iOS에서 이러한 기능을 사용하는 방법에 대해 설명합니다.

Important

Apple에서는 개발자가 유럽 연합의 GDPR(일반 데이터 보호 규정)을 제대로 처리하는 데 도움이 되는 도구를 제공합니다.

요구 사항

  • 안정적인 최신 버전의 Xamarin.iOS
  • Xcode 10
  • Mac용 Visual Studio 또는 Visual Studio 2019.

iCloud 개발 준비

Apple 프로비저닝 포털과 프로젝트 자체에서 iCloud를 사용하도록 애플리케이션을 구성해야 합니다. iCloud용으로 개발하거나 샘플을 사용해 보기 전에 아래 단계를 수행합니다.

iCloud에 액세스하도록 애플리케이션을 올바르게 구성하려면 다음을 수행합니다.

  • TeamID 찾기 - developer.apple.com 로그인하고 멤버 센터 > 계정 > 개발자 계정 요약을 방문하여 팀 ID(또는 단일 개발자의 개별 ID)를 가져옵니다. 10자 문자열( 예: A93A5CM278 )이 됩니다. 이는 "컨테이너 식별자"의 일부를 형성합니다.

  • 새 앱 ID 만들기 - 앱 ID를 만들려면 Device Provisioning 가이드의 스토어 기술 프로비전 섹션에 설명된 단계를 따르고 iCloud를 허용 서비스로 검사 합니다.

Check iCloud as an allowed service

  • 새 프로비전 프로필 만들기 - 프로비전 프로필을 만들려면 Device Provisioning 가이드설명된 단계를 따릅니다.

  • Entitlements.plist 에 컨테이너 식별자를 추가합니다. 컨테이너 식별자 형식은 다음과 같습니다 TeamID.BundleID. 자세한 내용은 권한 작업 가이드를 참조 하세요 .

  • 프로젝트 속성 구성 - Info.plist 파일에서 번들 식별자가 ID를 만들 때 번들 ID 집합과 일치하는지 확인합니다. iOS 번들 서명은 iCloud App Service와 함께 앱 ID가 포함된 프로비저닝 프로필사용자 지정 권한 파일을 선택합니다. 이 작업은 모두 Visual Studio의 프로젝트 속성 창에서 수행할 수 있습니다.

  • 디바이스에서 iCloud를 사용하도록 설정 - 설정 > iCloud로 이동하여 디바이스가 로그인되었는지 확인합니다. 문서 및 데이터 옵션을 선택하고 켭니다.

  • 디바이스를 사용하여 iCloud 를 테스트해야 합니다. 시뮬레이터에서는 작동하지 않습니다. 실제로 iCloud의 작동을 확인하려면 둘 이상의 디바이스가 모두 동일한 Apple ID로 로그인되어 있어야 합니다.

키-값 스토리지

키-값 스토리지는 사용자가 책이나 잡지에서 본 마지막 페이지와 같이 디바이스 간에 유지되기를 원하는 적은 양의 데이터를 위한 것입니다. 키-값 스토리지는 데이터를 백업하는 데 사용하면 안 됩니다.

키-값 스토리지를 사용할 때 알아야 할 몇 가지 제한 사항이 있습니다.

  • 최대 키 크기 - 키 이름은 64바이트를 초과할 수 없습니다.

  • 최대값 크기 - 단일 값으로 64킬로바이트 이상을 저장할 수 없습니다.

  • 의 최대 키-값 저장소 크기 - 애플리케이션은 최대 64KB의 키-값 데이터만 총 저장할 수 있습니다. 해당 제한을 초과하는 키를 설정하려는 시도는 실패하고 이전 값은 유지됩니다.

  • 데이터 형식 - 문자열, 숫자 및 부울과 같은 기본 형식만 저장할 수 있습니다.

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...
});

그러면 코드에서 변경된 키 목록을 사용하여 로컬 복사본을 업데이트하거나 새 값으로 UI를 업데이트하는 등의 작업을 수행할 수 있습니다.

가능한 변경 이유는 ServerChange(0), InitialSyncChange(1) 또는 QuotaViolationChange(2)입니다. 필요에 따라 이유에 액세스하고 다른 처리를 수행할 수 있습니다(예: QuotaViolationChange결과로 일부 키를 제거해야 할 수 있음).

문서 스토리지

iCloud Document Storage는 앱(및 사용자)에게 중요한 데이터를 관리하도록 설계되었습니다. 앱이 실행되어야 하는 파일 및 기타 데이터를 관리하는 동시에 모든 사용자의 디바이스에서 iCloud 기반 백업 및 공유 기능을 제공하는 데 사용할 수 있습니다.

이 다이어그램은 모든 항목이 어떻게 함께 맞는지 보여 줍니다. 각 디바이스에는 로컬 스토리지(UbiquityContainer)에 저장된 데이터가 있으며 운영 체제의 iCloud 디먼은 클라우드에서 데이터를 보내고 받는 작업을 처리합니다. 동시 액세스를 방지하려면 FilePresenter/FileCoordinator를 통해 UbiquityContainer에 대한 모든 파일 액세스를 수행해야 합니다. 클래스는 UIDocument 이를 구현합니다. 이 예제에서는 UIDocument를 사용하는 방법을 보여 줍니다.

The document storage overview

iCloudUIDoc 예제에서는 단일 텍스트 필드를 포함하는 간단한 UIDocument 하위 클래스를 구현합니다. 텍스트는 a 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 문서 찾기 및 열기 - iCloud 문서를 사용하고 NSFileManagerNSPredicate 찾아 엽니다.

  4. iCloud 문서 표시 - UI 컨트롤과 상호 작용할 수 있도록 사용자의 UIDocument 속성을 노출합니다.

  5. iCloud 문서 저장 - UI의 변경 내용이 디스크 및 iCloud에 유지되는지 확인합니다.

모든 iCloud 작업은 비동기적으로 실행되거나 실행되어야 하므로 문제가 발생할 때까지 기다리는 동안 차단되지 않습니다. 샘플에서 이 작업을 수행하는 세 가지 방법이 표시됩니다.

스레드 - 초기 호출 GetUrlForUbiquityContainer 에서 AppDelegate.FinishedLaunching 기본 스레드를 차단하지 않도록 다른 스레드에서 수행됩니다.

NotificationCenter - 완료와 같은 NSMetadataQuery.StartQuery 비동기 작업이 있을 때 알림에 등록합니다.

완료 처리기 - 같은 UIDocument.Open비동기 작업을 완료할 때 실행할 메서드를 전달합니다.

UbiquityContainer 액세스

iCloud Document Storage를 사용하는 첫 번째 단계는 iCloud를 사용할 수 있는지 여부와 "유비쿼터스 컨테이너"(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;
});

샘플은 그렇지 않지만 앱이 포그라운드에 올 때마다 GetUrlForUbiquityContainer를 호출하는 것이 좋습니다.

UIDocument 하위 클래스 만들기

모든 iCloud 파일 및 디렉터리(예: UbiquityContainer 디렉터리에 저장된 모든 항목)는 NSFileManager 메서드를 사용하여 관리되어야 하며, NSFilePresenter 프로토콜을 구현하고 NSFileCoordinator를 통해 작성해야 합니다. 이 모든 작업을 수행하는 가장 간단한 방법은 직접 작성하는 것이 아니라 모든 작업을 수행하는 서브클래스 UIDocument입니다.

iCloud를 사용하려면 UIDocument 하위 클래스에서 구현해야 하는 두 가지 메서드만 있습니다.

  • LoadFromContents - 파일 내용의 NSData를 전달하여 모델 클래스/es로 압축을 풉니다.

  • ContentsForType - 디스크(및 클라우드)에 저장할 모델 클래스/es의 NSData 표현을 제공할 것을 요청합니다.

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 코드는 해당 파일 이름을 만들고 구체적으로 찾습니다 NSMetadataQueryNSPredicate.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를 표시하는 것은 다른 모델 클래스와 다르지 않아야 합니다. 속성은 UI 컨트롤에 표시되고 사용자가 편집한 다음 모델에 다시 기록될 수 있습니다.

예제 iCloudUIDoc\MonkeyDocumentViewController.cs MonkeyDocument 텍스트를 UITextView에 표시합니다. ViewDidLoad 는 메서드에서 보낸 알림을 수신 대기합니다 MonkeyDocument.LoadFromContents . LoadFromContents 는 iCloud에 파일에 대한 새 데이터가 있을 때 호출되므로 해당 알림은 문서가 업데이트되었음을 나타냅니다.

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

샘플 코드 알림 처리기는 충돌 감지 또는 해결 없이 UI를 업데이트하는 메서드를 호출합니다.

[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 문서 저장

iCloud에 UIDocument를 추가하려면 직접 호출 UIDocument.Save 하거나(새 문서에만 해당) 사용하여 NSFileManager.DefaultManager.SetUbiquitious기존 파일을 이동할 수 있습니다. 예제 코드는 이 코드를 사용하여 보편성 컨테이너에 직접 새 문서를 만듭니다(여기에는 작업에 대한 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");
}

이후에 변경된 문서는 직접 "저장"되지 않고 변경되었음을 UpdateChangeCount알려 UIDocument 주며 디스크에 저장 작업을 자동으로 예약합니다.

doc.UpdateChangeCount (UIDocumentChangeKind.Done);

iCloud 문서 관리

사용자는 설정 통해 애플리케이션 외부의 "유비쿼터스 컨테이너"의 문서 디렉터리에서 iCloud 문서를 관리할 수 있습니다. 파일 목록을 보고 살짝 밀어 삭제할 수 있습니다. 애플리케이션 코드는 사용자가 문서를 삭제하는 상황을 처리할 수 있어야 합니다. 문서 디렉터리에 내부 애플리케이션 데이터를 저장하지 마세요.

Managing iCloud Documents workflow

또한 사용자는 디바이스에서 iCloud 지원 애플리케이션을 제거하려고 할 때 다른 경고를 수신하여 해당 애플리케이션과 관련된 iCloud 문서의 상태 알릴 수 있습니다.

Screenshot shows a warning for Document Updates Pending.

Screenshot shows a warning for Delete i Cloud.

iCloud Backup

iCloud에 백업하는 기능은 개발자가 직접 액세스하는 기능이 아니지만 애플리케이션을 디자인하는 방식은 사용자 환경에 영향을 줄 수 있습니다. Apple은 개발자가 iOS 애플리케이션에서 따를 수 있도록 iOS Data Storage 지침을 제공합니다.

가장 중요한 고려 사항은 앱이 사용자 생성되지 않은 큰 파일(예: 문제당 수백 메가바이트 이상의 콘텐츠를 저장하는 잡지 판독기 애플리케이션)을 저장하는지 여부입니다. Apple은 iCloud에 백업되고 사용자의 iCloud 할당량을 불필요하게 채울 이러한 종류의 데이터를 저장하지 않는 것을 선호합니다.

이와 같이 많은 양의 데이터를 저장하는 애플리케이션은 백업되지 않은 사용자 디렉터리 중 하나에 저장해야 합니다(예: 캐시 또는 tmp) 또는 iCloud가 백업 작업 중에 무시하도록 해당 파일에 플래그를 적용하는 데 사용합니다 NSFileManager.SetSkipBackupAttribute .

요약

이 문서에서는 iOS 5에 포함된 새로운 iCloud 기능을 소개했습니다. iCloud를 사용하도록 프로젝트를 구성하는 데 필요한 단계를 검사한 다음 iCloud 기능을 구현하는 방법에 대한 예제를 제공했습니다.

키-값 스토리지 예제에서는 iCloud를 사용하여 NSUserPreferences가 저장되는 방식과 유사한 소량의 데이터를 저장하는 방법을 보여 줍니다. UIDocument 예제에서는 iCloud를 통해 여러 디바이스에서 더 복잡한 데이터를 저장하고 동기화하는 방법을 보여 줬습니다.

마지막으로 iCloud Backup 추가가 애플리케이션 디자인에 미치는 영향에 대한 간략한 논의를 포함했습니다.