iCloud基础知识和代码示例

时间:2011-10-17 14:58:10

标签: iphone objective-c ios cocoa-touch icloud

作为一名初学者,我正在努力应对iCloud。有一些样本,但它们通常非常详细(在开发者论坛上有一个用于iCloud和CoreData的大量)。 apple docs可以,但我仍然看不到大局。所以请耐心等待,其中一些问题非常重要,但可能很容易回答。

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

enter image description here

txt文件被推送到云端并可供所有设备使用。完美无缺,但是:

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

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

如果用户未启用iCloud,则不会发生任何事情。如何让非iCloud用户仍然可以使用我的文本应用程序?或者我只是忽略它们?我是否需要为非iCloud用户编写单独的功能?即我只是从文档文件夹中加载text.txt的函数?

Apple writes

  

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

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

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

也许我的主要问题是对iCloud如何工作的根本误解。当我创建一个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会话视频中找到的示例。我将它剥离到最低限度。我不确定我的MVC结构是否有用。该模型在AppDelegate中并不理想。欢迎任何改善它的建议。


编辑:我试图提取主要问题并将其发布到[此处]。4


概述:

Overview

从云端加载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 (i.e. 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];

}

5 个答案:

答案 0 :(得分:20)

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

  
    

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

  
     

将文件或目录移至iCloud:

     

在应用沙箱中本地创建文件或目录。在   使用时,文件或目录必须由文件提供者管理,例如   作为UIDocument对象。

     

使用URLForUbiquityContainerIdentifier:方法检索网址   对于要存储的iCloud容器目录   项目。使用容器目录URL来构建新的URL   指定项目在iCloud中的位置。打电话给   setUbiquitous:itemAtURL:destinationURL:error:NSFileManager的方法   将项目移动到iCloud。切勿从您的应用中调用此方法   主线;这样做可能会阻止你的主线程进行扩展   一段时间或导致您的应用程序自己的文件死锁   主持人。将文件或目录移动到iCloud时,系统   将该项目从您的应用沙箱中复制到私有本地   目录,以便iCloud守护程序可以监视它。甚至   虽然该文件不再在您的沙箱中,但您的应用仍然已满   访问它。虽然该文件的副本仍然是当前的本地副本   设备,该文件也发送到iCloud,以便它可以分发   到其他设备。 iCloud守护程序处理所有制作工作   确保本地副本是相同的。所以从这个角度来看   你的应用程序,该文件就在iCloud中。

     

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

但是,如果你深入研究有关setUbiquitous的文档,你会发现:

  

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

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

答案 1 :(得分:5)

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

例:

  1. 新用户
    • 有icloud - 在icloud中创建文档
    • no icloud - 在本地创建文档
  2. 现有用户
    • 有icloud
      • 刚刚添加 - 将本地文档迁移到icloud
      • 不只是添加 - 打开/保存文档到icloud
    • 没有icloud
      • 刚删除 - 将以前的icloud文档迁移到本地
      • 不仅仅是删除 - 打开/保存文档到本地
  3. 如果有人删除了iCloud,那么对无处不在的URL的调用是否会返回nil?如果是这种情况,我该如何将文档迁移回本地存储?我现在要创建一个用户pref,但似乎有点解决方法。

    我觉得我在这里遗漏了一些明显的东西,所以如果有人能看到它,请加入。

答案 2 :(得分:4)

如果您希望用户能够在iOS 5.0之前的设备之间共享文本,那么您将不得不在iCloud之前执行每个人必须执行的操作并将信息移动到您自己的服务器。

您真正需要的只是一个服务器,可让您的应用保存其文本文件并将其与用户帐户相关联。

您需要用户创建帐户,并且您需要自己管理流程,将一台设备上的新信息移动到您自己的“云端”中。

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

显然,对于iOS 5.0设备,您可能希望在自己的云中检测iOS 5.0之前设备的已更改文件,并且还能够与iCloud通信。

答案 3 :(得分:3)

您似乎没有像iOS5 / notIOS5问题那样在iCloud / notICloud问题上苦苦挣扎。

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

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

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

答案 4 :(得分:1)

您对“在iCloud中处理文件的方式与处理应用程序沙箱中的所有其他文件的方式相同”感到困惑。这适用于像Keynote和Numbers这样的东西,你保存了一堆文件,如果你有iCloud,它们会开始神奇地同步。

但是,您正在构建依赖于类似iCloud的功能的东西。你不能坚持这个陈述,因为你的应用程序依赖于iCloud来呈现任何可以按照它的方式工作的东西。您将需要关闭您的应用程序并简单地说“请设置iCloud以使其工作”或复制类似iCloud的功能(您自己或其他人的),无论如何。