iCloud基础知识和代码示例

作为初学者,我正在与iCloud苦苦挣扎。 有一些样本,但通常是非常详细的(在开发者论坛上有一个用于iCloud和CoreData的是巨大的)。 苹果文档是可以的,但我仍然看不到大的图片。 所以请耐心等待,其中一些问题是相当基本的,但可能很容易回答。

上下文:我有一个非常简单的iCloud应用程序运行(下面的完整示例代码)。 显示给用户的只有一个UITextView,他/她的input保存在一个名为text.txt的文件中。

在这里输入图像说明

txt文件被推送到云,并提供给所有设备。 完美的作品,但:

主要问题:那些不使用iCloud的用户呢?

当我启动我的应用程序(请参阅下面的代码),我检查用户是否启用了iCloud。 如果启用iCloud,一切都很好。 该应用程序继续前进,并在云中寻找text.txt。 如果find,它将加载并显示给用户。 如果在云中没有findtext.txt,它只会创build一个新的text.txt文件,并将其显示给用户。

如果用户没有启用iCloud,则不会发生任何事情。 我将如何使非iCloud用户仍可以使用我的文本应用程序? 还是我简单地忽略它们? 我需要为非iCloud用户编写单独的函数吗? 即function,我只是从文档文件夹中加载一个text.txt?

苹果写道 :

在iCloud中对待文件的方式与对待应用沙箱中所有其他文件的方式相同。

但是,在我的情况下,没有“正常”的应用程序沙箱了。 它在云端。 或者,我总是首先从磁盘加载我的text.txt,然后检查与iCloud是否有更新的东西?

相关问题:文件结构 – 沙盒与云

也许我的主要问题是如何iCloud应该工作的根本误解。 当我创build一个UIDocument的新实例时,我将不得不覆盖两个方法。 首先- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError从云中获取文件,然后-(id)contentsForType:(NSString *)typeName error:(NSError **)outError将文件导入到云中。

我是否必须将单独的函数合并到一起,这些函数还将text.txt的本地副本保存到我的沙盒中? 这是否适用于非iCloud用户? 据我了解iCloud,它会自动保存text.txt的本地副本。 所以我不需要任何东西保存任何东西到我的应用程序的“旧”沙箱(即,因为它曾经是在旧的iCloud日子之前)。 现在,我的沙箱完全是空的,但是我不知道这是否正确。 我应该在那里保留另一个text.txt的副本吗? 这感觉就像我的数据结构混乱…因为有一个在云中的text.txt,一个在我的设备上的iCloud沙箱(这将工作,即使我离线),第三个在旧的沙箱我的应用…


我的代码:一个简单的iCloud示例代码

这个松散的基础是我在开发者论坛和WWDC会话video中find的一个例子。 我把它剥离到最低限度。 我不确定我的MVC结构是否有用。 该模型是在AppDelegate这是不理想的。 任何build议,使其更好,是值得欢迎的。


编辑:我试图提取主要问题,并张贴在这里[这里]。 4


概述:

概观

从云中加载text.txt的最重要的位:

 // AppDelegate.h // iCloudText #import <UIKit/UIKit.h> @class ViewController; @class MyTextDocument; @interface AppDelegate : UIResponder <UIApplicationDelegate> { NSMetadataQuery *_query; } @property (strong, nonatomic) UIWindow *window; @property (strong, nonatomic) ViewController *viewController; @property (strong, nonatomic) MyTextDocument *document; @end // AppDelegate.m // iCloudText #import "AppDelegate.h" #import "MyTextDocument.h" #import "ViewController.h" @implementation AppDelegate @synthesize window = _window; @synthesize viewController = _viewController; @synthesize document = _document; - (void)dealloc { [_window release]; [_viewController release]; [super dealloc]; } - (void)loadData:(NSMetadataQuery *)query { // (4) iCloud: the heart of the load mechanism: if texts was found, open it and put it into _document; if not create it an then put it into _document if ([query resultCount] == 1) { // found the file in iCloud NSMetadataItem *item = [query resultAtIndex:0]; NSURL *url = [item valueForAttribute:NSMetadataItemURLKey]; MyTextDocument *doc = [[MyTextDocument alloc] initWithFileURL:url]; //_document = doc; doc.delegate = self.viewController; self.viewController.document = doc; [doc openWithCompletionHandler:^(BOOL success) { if (success) { NSLog(@"AppDelegate: existing document opened from iCloud"); } else { NSLog(@"AppDelegate: existing document failed to open from iCloud"); } }]; } else { // Nothing in iCloud: create a container for file and give it URL NSLog(@"AppDelegate: ocument not found in iCloud."); NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; NSURL *ubiquitousPackage = [[ubiq URLByAppendingPathComponent:@"Documents"] URLByAppendingPathComponent:@"text.txt"]; MyTextDocument *doc = [[MyTextDocument alloc] initWithFileURL:ubiquitousPackage]; //_document = doc; doc.delegate = self.viewController; self.viewController.document = doc; [doc saveToURL:[doc fileURL] forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) { NSLog(@"AppDelegate: new document save to iCloud"); [doc openWithCompletionHandler:^(BOOL success) { NSLog(@"AppDelegate: new document opened from iCloud"); }]; }]; } } - (void)queryDidFinishGathering:(NSNotification *)notification { // (3) if Query is finished, this will send the result (ie either it found our text.dat or it didn't) to the next function NSMetadataQuery *query = [notification object]; [query disableUpdates]; [query stopQuery]; [self loadData:query]; [[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query]; _query = nil; // we're done with it } -(void)loadDocument { // (2) iCloud query: Looks if there exists a file called text.txt in the cloud NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; _query = query; //SCOPE [query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]]; //PREDICATE NSPredicate *pred = [NSPredicate predicateWithFormat: @"%K == %@", NSMetadataItemFSNameKey, @"text.txt"]; [query setPredicate:pred]; //FINISHED? [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query]; [query startQuery]; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSLog(@"AppDelegate: app did finish launching"); self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; // Override point for customization after application launch. if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) { self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController_iPhone" bundle:nil] autorelease]; } else { self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController_iPad" bundle:nil] autorelease]; } self.window.rootViewController = self.viewController; [self.window makeKeyAndVisible]; // (1) iCloud: init NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; if (ubiq) { NSLog(@"AppDelegate: iCloud access!"); [self loadDocument]; } else { NSLog(@"AppDelegate: No iCloud access (either you are using simulator or, if you are on your phone, you should check settings"); } return YES; } @end 

UIDocument

 // MyTextDocument.h // iCloudText #import <Foundation/Foundation.h> #import "ViewController.h" @interface MyTextDocument : UIDocument { NSString *documentText; id delegate; } @property (nonatomic, retain) NSString *documentText; @property (nonatomic, assign) id delegate; @end // MyTextDocument.m // iCloudText #import "MyTextDocument.h" #import "ViewController.h" @implementation MyTextDocument @synthesize documentText = _text; @synthesize delegate = _delegate; // ** READING ** - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError { NSLog(@"UIDocument: loadFromContents: state = %d, typeName=%@", self.documentState, typeName); if ([contents length] > 0) { self.documentText = [[NSString alloc] initWithBytes:[contents bytes] length:[contents length] encoding:NSUTF8StringEncoding]; } else { self.documentText = @""; } NSLog(@"UIDocument: Loaded the following text from the cloud: %@", self.documentText); // update textView in delegate... if ([_delegate respondsToSelector:@selector(noteDocumentContentsUpdated:)]) { [_delegate noteDocumentContentsUpdated:self]; } return YES; } // ** WRITING ** -(id)contentsForType:(NSString *)typeName error:(NSError **)outError { if ([self.documentText length] == 0) { self.documentText = @"New Note"; } NSLog(@"UIDocument: Will save the following text in the cloud: %@", self.documentText); return [NSData dataWithBytes:[self.documentText UTF8String] length:[self.documentText length]]; } @end 

VIEWCONTROLLER

 // // ViewController.h // iCloudText #import <UIKit/UIKit.h> @class MyTextDocument; @interface ViewController : UIViewController <UITextViewDelegate> { IBOutlet UITextView *textView; } @property (nonatomic, retain) UITextView *textView; @property (strong, nonatomic) MyTextDocument *document; -(void)noteDocumentContentsUpdated:(MyTextDocument *)noteDocument; @end // ViewController.m // iCloudText #import "ViewController.h" #import "MyTextDocument.h" @implementation ViewController @synthesize textView = _textView; @synthesize document = _document; -(IBAction)dismissKeyboard:(id)sender { [_textView resignFirstResponder]; } -(void)noteDocumentContentsUpdated:(MyTextDocument *)noteDocument { NSLog(@"VC: noteDocumentsUpdated"); _textView.text = noteDocument.documentText; } -(void)textViewDidChange:(UITextView *)theTextView { NSLog(@"VC: textViewDidChange"); _document.documentText = theTextView.text; [_document updateChangeCount:UIDocumentChangeDone]; } 

我只是重新阅读文档,似乎我的一般方法是错误的。 我应该先在沙盒中创build文件,然后将其移动到云端。 换句话说,苹果似乎build议我应该有三个版本的同一个文件在任何时候:一个在我的应用程序的目录,一个在我的设备的iCloud的恶魔目录(这也可以访问,如果离线)和一个云端:

应用程序使用相同的技术来pipe理iCloud中用于本地文件和目录的文件和目录。 iCloud中的文件和目录仍然只是文件和目录。 您可以打开它们,创build它们,移动它们,复制它们,读取和写入它们,删除它们,或者您可能想要执行的其他任何操作。 本地文件和目录以及iCloud文件和目录之间的唯一区别就是用于访问它们的URL。 而不是URL相对于您的应用程序的沙箱,iCloud文件和目录的URL是相对于相应的iCloud容器目录。

将文件或目录移动到iCloud:

在应用程序沙箱中本地创build文件或目录。 在使用中,文件或目录必须由文件展示器(例如UIDocument对象)pipe理。

使用URLForUbiquityContainerIdentifier:方法来检索要存储项目的iCloud容器目录的URL。 使用容器目录URL构build一个新的URL,指定项目在iCloud中的位置。 调用NSFileManager的setUbiquitous:itemAtURL:destinationURL:error:方法将项目移动到iCloud。 切勿从应用程序的主线程中调用此方法; 这样做可能会阻塞您的主线程很长一段时间,或导致与您的应用程序自己的文件演示者之一的僵局。 将文件或目录移动到iCloud时,系统会将该项目从应用程序沙箱中复制到专用本地目录中,以便iCloud守护程序可以监视该项目。 即使该文件不再位于沙盒中,您的应用程序仍然可以完全访问它。 尽pipe该文件的副本保留在当前设备的本地,但该文件也会发送到iCloud,以便将其分发到其他设备。 iCloud守护进程处理确保本地副本相同的所有工作。 所以从你的应用程序的angular度来看,这个文件只是在iCloud中。

您对iCloud中的文件或目录所做的所有更改都必须使用文件协调器对象进行。 这些更改包括移动,删除,复制或重命名项目。 文件协调器确保iCloud守护进程不会同时更改文件或目录,并确保通知其他感兴趣的各方您所做的更改。

但是,如果您深入挖掘关于setUbiquitous的文档,您会发现:

使用此方法将文件从其当前位置移动到iCloud。 对于位于应用程序沙箱中的文件这涉及从沙箱目录中物理删除文件 。 (该系统扩展了您的应用程序的沙箱权限,使其能够访问移至iCloud的文件。)您也可以使用此方法将文件移出iCloud并移回本地目录。

所以这似乎意味着一个文件/目录从本地沙箱中被删除并移动到云中。

我一直在使用你的例子,我喜欢它帮助我掌握iCloud的基础知识。 现在,我正在为自己的应用程序争论自己的应用程序,它必须支持应用程序的现有用户与本地存储的内容谁可能或可能不会使用iCloud创build这些情况,据我所知:

案例:

  1. 新用户
    • 有icloud – 在icloud中创build文件
    • 没有icloud – 在本地创build文档
  2. 现有用户
    • 有icloud
      • 刚添加 – 将本地文档迁移到icloud
      • 不只是添加 – 打开/保存文档icloud
    • 没有icloud
      • 刚刚删除 – 将以前的icloud文档迁移到本地
      • 不只是删除 – 打开/保存文档到本地

如果有人删除iCloud – 不会调用无处不在的url返回零吗? 如果是这种情况,我如何将文档迁移回本地存储? 我现在要创build一个用户pref,但似乎是一个解决方法。

我觉得我在这里错过了一些明显的东西,所以如果任何人都可以看到它,请join。

如果您希望用户能够在iOS 5.0之前的设备之间共享文本,则必须先做好每个人在iCloud之前所做的工作,然后将信息移动到您自己的服务器上。

所有你真正需要的是一个服务器,让你的应用程序保存其文本文件,并将它们与用户帐户相关联。

您需要用户创build一个帐户,您需要自己pipe理这个过程,将一台设备上的新信息转移到您自己的“云”中。

用户将在其他设备上注册相同的帐户,并且需要注意检测其他设备何时将数据移动到您自己的云上,并使用新的信息更新当前设备。

显然,对于iOS 5.0设备,您可能需要检测自己云中的iOS 5.0以前版本设备的更改文件,还可以与iCloud进行通信。

似乎并不像iCloud / notICloud问题一样在iOS5 / notIOS5问题上挣扎。

如果您的部署目标是iOS5,那么只需使用UIDocument结构。 如果它无处不在,那么你的NSMetaDataQuery将在云中find它; 如果没有,它会在设备上find它。

另一方面,如果您希望提供5.0以前版本的应用程序,则需要有条件地检查运行的iOS版本是否为5.0或更高版本。 如果是的话使用UIDocument; 如果不是那么旧的方式读/写数据。

我的方法是编写一个条件saveData方法来检查iOS5。 如果存在,我更新更改计数(或使用撤消pipe理器)。 在你的情况下,textViewDidChange会调用这个方法。 如果不是的话,那么它会以旧的方式保存到磁盘。 在加载时,会发生相反的情况。

您被“处理iCloud中的文件与处理应用沙箱中所有其他文件的方式相同”所迷惑。 对于像Keynote和Numbers这样的东西,你可以保留一堆文件,如果你有iCloud,他们就可以神奇地开始同步了。

不过,你正在build立一些依赖类似于iCloud的function。 你不能坚持这种说法,因为你的应用程序依赖于iCloud,以任何方式工作。 你要么closures你的应用程序,只需说“请设置iCloud的这个工作”或重复类似iCloud的function(你自己或别人的),你可以随时使用,无论。