---------------------------------------------



另一篇相关日志:



【iOS开发】iCloud开发 分析 : http://blog.sina.com.cn/s/blog_693de6100101jur4.html



---------------------------------------------





iCloud Document Storage 和 Key-Value Data Storage 教程与简单示例



--------------------------------------------------



UIDocument



--------------------------------------------------


iCloud Document Storage开发



测试


iCloud目前只能在真实设备中测试,不能在Simulator中测试



Display Set


在iTunesConnect中新建"iCloud Manage Display Sets",iCloud的文档和数据存储在display sets中。多个应用可以引用和存储数据到同一个display set。(iTunesConnect手册上有这一步,但是很多教程,包括iOS App Programming Guide也没有提到,可能不需要了)


APP ID 和 开发证书

iOS Provisioning Portal中,创建启用iCloud的App ID,现有的App ID也可以编辑并启用iCloud,App ID必须不包含通配符"*"




针对上面的App ID,创建新的Development Provisioning Profile,下载后双击使用Xcode打开完成安装,并设置Code Signing。




Xcode entitlements


在Xcode中配置应用的iCloud Entitlements,Target->Summary->Entitlements,勾选后Xcode会自动生成


iCloud Key-Value Store:

com.company.App


iCloud Containers:

com.company.App


Keychain Access Groups:

com.company.App




这里

Xcode自动生成的Entitlements使用了通配符,我们需要手工修改PROJECT.Entitlements文件:

iCloud Key-Value Store: TeamIdentifier.com.company.App


iCloud Containers:

TeamIdentifier

.com.company.App

Keychain Access Groups:

ApplicationIdentifierPrefix

.com.company.App


其中TeamIdentifier是申请 IDP 时拿到的标识,可以在MemberCenter -> Your Account -> Account Summary中找到(

http://developer.apple.com/membercenter/index.action


ApplicationIdentifierPrefix 则是注册App ID时使用的前缀。




其实简单的办法是用编辑器打开上面下载的Development Provisioning Profile,直接在里面就可以找到这两个字符串。




源代码定义Ubiquity Container URL


定义一个常量字符串,我们代码里面使用它来构造iCloud storage的URL,实际上和Entitlement中定义的字符串是一样的。


#define UBIQUITY_CONTAINER_URL @"ABCDEF12345.com.yourdomain.icloudapp




UIDocument


iOS 5为iCloud引入的file presenter,创建和管理文档及其内容,极大地方便了开发。对本地文件使用UIDocument也能带来一些好处。如后台队列的异步读写、处理版本冲突、自动文档保存等。




继承UIDocument


contentsForType:error:


UIDocument对象将数据写入文件或文档时调用,这个方法负责收集要写入的数据,并返回NSData或NSFileWrapper对象。




loadFromContents:ofType:error:


UIDocument对象从文件或文档中读取数据时调用,并传递从文件或文档中已经读取到的数据给这个方法。方法负责把这些数据装载到应用的内部数据model




文档状态和冲突解决


文档状态:


UIDocumentStateNormal – 文档已打开并允许用户编辑


UIDocumentStateClosed – 文档当前已关闭,读取文档时出错也可能是这个状态


UIDocumentStateInConflict – 检测到文档的多个版本冲突


UIDocumentStateSavingError – 试图保存文档时出错


UIDocumentStateEditingDisabled – 文档繁忙,当前编辑不安全




文档状态变化时会发送 UIDocumentStateChangedNotification 通知,注册这个通知就能监测文档状态,及时处理出错、冲突等。




解决冲突:


  1. 如果可行,合并冲突版本
  2. 如果不丢失数据,丢弃冲突版本
  3. 提示用户,选择要保留的版本

iCloud


理想情况下,应该允许用户指定哪些文件存储在iCloud,哪些文件存储在本地


之前已经添加到iCloud的文档,不能使用绝对路径来访问!应用应该通过名字在iCloud storage中查找文档。


存储到iCloud的文档,应该放在应用的Documents目录




使用 URLForUbiquityContainerIdentifier: 方法检测iCloud是否可用,传递已经定义好的Container URL,也可以传递nil,表示Entitlements中定义的第一个URL。方法返回nil表示iCloud不可用,返回URL表示可用。




拿到这个URL后,再对它调用 URLByAppendingPathComponent:@"Documents" 来组建一个路径。




默认情况下,当前应用的iCloud storage中并没有这个Documents目录,因此我们需要首先创建它。




然后再使用 NSMetadataQuery 查找iCloud中是否存在我们要的文档,查找过程会在另一个线程中进行,完成后会通过 NSMetadataQueryDidFinishGatheringNotification 通知告诉你注册的observer




本地文件移动到iCloud


调用 setUbiquitous: itemAtURL: 方法,传递YES表示本地文件移动到iCloud,传递NO则表示反向移动。




设备配置


在 Settings 应用中启用设备的iCloud Document and Data Storage,运行程序测试,此时可以在 Settings 应用中看到已经上传到iCloud中的文档




完整例子


MyDocument.h


@interface MyDocument : UIDocument


{


 


}


@property (strong, nonatomic) NSString *userText;




MyDocument.m


-(id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError


{


 


}




-(BOOL) loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError 


{


 


     


 


     


 


 


}




iCloudStoreViewController.h


@interface iCloudStoreViewController : UIViewController


{


 


 


 


 


 


}


@property (strong, nonatomic) IBOutlet UITextView *textView;


@property (strong, nonatomic) NSURL *documentURL;


@property (strong, nonatomic) MyDocument *document;


@property (strong, nonatomic) NSURL *ubiquityURL;


@property (strong, nonatomic) NSMetadataQuery *metadataQuery;


-(IBAction)saveDocument;






iCloudStoreViewController.m


- (void)saveDocument


{


 


  [self.document saveToURL:ubiquityURL forSaveOperation:UIDocumentSaveForOverwriting


         completionHandler:^(BOOL success) {


             if (success){


                 NSLog(@"Saved to cloud for overwriting");


             } else {


                 NSLog(@"Not saved to cloud for overwriting");


             } 


         }];


}




- (void)viewDidLoad


{


 


  NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);


 


  NSString *dataFile = [docsDir stringByAppendingPathComponent: @"document.doc"];


 


 


  ubiquityURL = [[filemgr URLForUbiquityContainerIdentifier:UBIQUITY_CONTAINER_URL] URLByAppendingPathComponent:@"Documents"];


 



 


 


     


      [filemgr createDirectoryAtURL:ubiquityURL withIntermediateDirectories:YES attributes:nil error:nil];


 


     


 



  ubiquityURL = [ubiquityURL URLByAppendingPathComponent:@"document.doc"];


 



 


 


 


             


  [metadataQuery setSearchScopes:[NSArray arrayWithObjects:NSMetadataQueryUbiquitousDocumentsScope,nil]];


  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(metadataQueryDidFinishGathering:) 


              name:NSMetadataQueryDidFinishGatheringNotification 


             


 


 


}




- (void)metadataQueryDidFinishGathering:(NSNotification *)notification


{


 


 


  [[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query];


 


 




 


  {        


     


     


     


     


      [document openWithCompletionHandler:


       ^(BOOL success) { 


           if (success){


               NSLog(@"Opened cloud doc");


               textView.text = document.userText;


           } else {


               NSLog(@"Not opened cloud doc");


           } 


       }];


 


     


     



      [document saveToURL:ubiquityURL 


         forSaveOperation: UIDocumentSaveForCreating


        completionHandler:^(BOOL success) { 


           


                NSLog(@"Saved to cloud");   


            }  else {


               


           


       


 


}




发布与提交


开发测试完成后,在 iOS Provisioning Portal 中为 App ID 

创建新的Distribution Provisioning Profile,打包并Submit应用










--------------------------------------------------



NSUbiquitousKeyValueStore 



--------------------------------------------------



iCloud Key-Value Data Storage开发

iCloud Key-Value Data Storage主要用于应用的小量非关键数据在多个设备间共享,支持的数据包括:NSString, NSDate, NSArray, NSData, Boolean, NSDictionary, NSNumber等对象
NSUbiquitousKeyValueStore类
NSUbiquitousKeyValueStore *keyStore = 
[[NSUbiquitousKeyValueStore alloc] init];
[keyStore setString:@”Saved String” forKey:@"MyString"];

然后调用:
[keyStore synchronize];

保存在本地,以便稍后同步到iCloud,但是调用synchronize方法并不会立即将本地保存的数据同步到iCloud。iOS自己会在适当的时候将这些本地数据同步到iCloud Key-value Data Storage

获取key-value值:
NSString *storedString = [keyStore stringForKey:@"MyString"];

iCloud Key-Value不会产生冲突,最后上传的总是最新的值。

observer可以通过 NSUbiquitousKeyValueStoreDidChangeExternallyNotification 通知来监听key-value storage的变化。
key-value存储超出限制时也会递送这个通知,并传递给你 NSUbiquitousKeyValueStoreQuotaViolationChange 常量。

完整例子

iCloudKeysViewController.h
@interface iCloudKeysViewController
UITextField *textField;
NSUbiquitousKeyValueStore *keyStore;
}
@property (strong, nonatomic) IBOutlet UITextField *textField;
-(IBAction)saveKey;
@end

iCloudKeysViewController.m
 -(void)saveKey
 {
     [keyStore setString:textField.text forKey:@"MyString"];
     [keyStore synchronize];

     NSLog(@"Save key");
 }


 - (void)viewDidLoad
 {
     [super viewDidLoad];
 // Do any additional setup after loading the view, typically from a nib.
     keyStore = [NSUbiquitousKeyValueStore defaultStore]; 
    

     NSString *storedString = [keyStore stringForKey:@"MyString"];

     if (storedString != nil)
     {
         textField.text = storedString;
     }

     [[NSNotificationCenter defaultCenter] addObserver:self
             selector:@selector(ubiquitousKeyValueStoreDidChange:) 
             name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
             object:keyStore];


 }


 -(void) ubiquitousKeyValueStoreDidChange: (NSNotification *)notification
 {
     NSLog(@"External Change detected");
     UIAlertView *alert = [[UIAlertView alloc]
                           initWithTitle:@"Change detected"
                           message:@"Change detected"
                           delegate:nil 
                           cancelButtonTitle:@"Ok"
                           otherButtonTitles:nil, nil];
     [alert show];

     textField.text = [keyStore stringForKey:@"MyString"];
 }


--------------------------------------------------



--------------------------------------------------



在《萌塔塔》项目采用  NSUbiquitousKeyValueStore,借鉴userdefault的用法,有效解决。



难点在c++与oc之间准确的进行数据转换。