iCloud基础知识和代码示例

n.e*_*ind 83 iphone cocoa-touch objective-c ios icloud

作为初学者,我正在努力与iCloud合作.有一些样本,但它们通常非常详细(在开发者论坛上有一个用于iCloud和CoreData的大量).在苹果的文档都OK,但我仍然无法看到大局.所以请耐心等待,其中一些问题非常重要,但可能很容易回答.

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

在此输入图像描述

txt文件被推送到云端并可供所有设备使用.效果很好,但是:

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

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

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

Apple写道:

处理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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

视图控制器

//
//  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];

}
Run Code Online (Sandbox Code Playgroud)

n.e*_*ind 21

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

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

要将文件或目录移动到iCloud:

在应用程序沙箱中本地创建文件或目录.在使用时,文件或目录必须由文件展示器管理,例如UIDocument对象.

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

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

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

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

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


ear*_*ian 5

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

案例:

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

如果有人删除iCloud - 对无处不在的URL的调用是否会返回nil?如果是这种情况,我该如何将文档迁移回本地存储?我现在要创建一个用户pref,但似乎有点变通方法.

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