【问题标题】:iCloud basics and code sample [closed]iCloud 基础知识和代码示例 [关闭]
【发布时间】:2011-12-09 09:15:16
【问题描述】:

作为一个初学者,我在 iCloud 上苦苦挣扎。有一些示例,但它们通常非常详细(在开发者论坛上有一个用于 iCloud 和 CoreData 的大量示例)。 apple docs 还可以,但我还是看不到大局。所以请耐心等待,其中一些问题非常基础,但可能很容易回答。

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

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

主要问题:不使用 iCloud 的用户怎么办?

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

如果用户没有启用 iCloud,什么都不会发生。如何让非 iCloud 用户仍然可以使用我的文本应用程序?还是我只是忽略它们?我需要为非 iCloud 用户编写单独的函数吗? IE。我只是从文档文件夹中加载一个 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


概述:

最重要的一点是从云端加载 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.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];

}

【问题讨论】:

  • 我真的建议将其分解为几个问题。我在这里看到了一些不同的问题,很难从你这里的文字墙中找出它们。我会将这个问题恢复为只是询问未启用 iCloud 的人该怎么做,并将其他问题(仅使用示例代码的相关部分)分解为单独的问题。它们是很好的问题,但我认为应该将它们分开。
  • @BradLarson 感谢您的评论。如果问题有点混乱,我很抱歉,但我认为主要问题(正如我试图指出的那样)是应用程序沙箱与 iCloud 沙箱问题。我提供了完整的代码(这是最短的 iCloud 代码示例,顺便说一句),因为我认为整个上下文对于了解正在发生的事情至关重要......但我可能只是打开另一个问题并将其链接回这个问题获得更大的图景。
  • @BradLarson 好的,我在这里提出了一个新问题:stackoverflow.com/questions/7798555/…
  • 对于那些仍在尝试处理 Core Data 和 iCloud 的人,请尝试此链接 ossh.com.au/design-and-technology/software-development/…
  • 不应关闭这实际上是我在 iCloud 上看到的更具建设性的帖子之一。

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


【解决方案1】:

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

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

将文件或目录移动到 iCloud:

在您的应用沙箱中本地创建文件或目录。 使用时,文件或目录必须由文件呈现器管理,例如 作为 UIDocument 对象。

使用 URLForUbiquityContainerIdentifier: 方法检索 URL 对于要存储的 iCloud 容器目录 物品。使用容器目录 URL 构建一个新的 URL 指定项目在 iCloud 中的位置。调用 setUbiquitous:itemAtURL:destinationURL:error: NSFileManager 的方法 将项目移动到 iCloud。永远不要从你的应用程序调用这个方法 主线程;这样做可能会阻塞你的主线程以延长 一段时间或导致您的应用程序自己的文件之一死锁 主持人。当您将文件或目录移动到 iCloud 时,系统 将该项目从您的应用沙箱中复制到私有本地 目录,以便它可以被 iCloud 守护进程监控。甚至 尽管该文件不再在您的沙箱中,但您的应用程序仍然有完整的 访问它。尽管该文件的副本仍然是当前文件的本地 设备,文件也被发送到 iCloud 以便分发 到其他设备。 iCloud 守护进程处理所有的制作工作 确保本地副本相同。所以从这个角度 您的应用程序,该文件只是在 iCloud 中。

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

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

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

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

【讨论】:

  • 网址链接已损坏...
【解决方案2】:

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

案例:

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

如果有人删除了 iCloud - 对无处不在的 URL 的调用不会返回 nil 吗?如果是这种情况,我该如何将文档迁移回本地存储?我现在将创建一个用户首选项,但似乎有点解决方法。

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

【讨论】:

  • 我应该补充一点,我想知道是否有一个类可以处理这些情况,所以我只是使用它而不必担心保存它的位置。
  • 看看developer.apple.com/library/ios/#documentation/DataManagement/…,它提供了一些示例代码,以确定是否应该将某些东西放在本地沙箱或云中。
  • 谢谢。我看过那个文档,但在我的 iCloud 任务早期,所以我忘记了它提供的代码。我将尝试调整您的示例以支持本地和远程。我仍然不清楚我们如何处理禁用 iCloud 的用户,因为我们丢失了无处不在的 URL,但我会破解并分享更新。
  • 所以在某种程度上,我们必须使用云的 URL 和本地沙箱的 PATH 有点愚蠢。如果 iCloud 可以为我们处理一切,那就太好了……但这样一来,我们基本上需要为我们打开的每个文件编写两种不同的方法。
  • 我刚刚重新阅读了您的帖子。我现在将用户的偏好(即用户想要/不想使用 iCloud)保存在 NSUserDefaults 中。这也是苹果建议的。我总是检查 iCloud 是否可以访问。如果它无法访问,我会告诉用户打开它——但前提是他们没有明确告诉应用程序他们不想使用它。否则对于那些不想使用 iCloud 的人来说会很烦人。一旦确定是否启用了 iCloud,我将遵循无处不在的 URL 路由并使用 UIDocument,或者像过去一样简单地从沙箱中打开文件。
【解决方案3】:

您对“对待 iCloud 中的文件就像对待应用沙箱中的所有其他文件一样”感到困惑。这适用于 Keynote 和 Numbers 等保存一堆文件的东西,如果你有 iCloud,它们就会开始神奇地同步。

但是,您正在构建的东西依赖于类似 iCloud 的功能。你不能坚持这样的说法,因为你的应用程序依赖于 iCloud 的存在,任何东西都可以按照预期的方式工作。您要么必须关闭您的应用程序并简单地说“请设置 iCloud 以使其工作”,要么复制您始终可以使用的类似 iCloud 的功能(您自己的或其他人的)。

【讨论】:

  • 谢谢。所以我想我必须选择是只做一个 iCloud 应用程序还是为那些关闭 iCloud 功能的人做某种混合。由于 iCloud 如此复杂,我倾向于选择仅 iCloud 的应用程序。谢谢。
【解决方案4】:

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

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

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

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

【讨论】:

    【解决方案5】:

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

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

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

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

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

    【讨论】:

    • 谢谢。所以换句话说,如果我不想支持 iOS 5 之前的设备,我只需使用 UIDocument 并忘记我的应用程序沙箱中 doc 目录的内容。
    • 差不多,但据我所知,您仍然在沙箱中有一个文档,UIDocument 将帮助您与 iCloud 进行协调,但您会被告知何时可以访问它...我自己仍在掌握这些东西!
    猜你喜欢
    • 2011-01-25
    • 2012-05-12
    • 2019-04-14
    • 1970-01-01
    • 1970-01-01
    • 2011-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多