Основы 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 пишет:

Рассматривайте файлы в 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, которая не идеальна. Любые предложения по его улучшению приветствуются.


EDIT: Я попытался извлечь главный вопрос и разместил его здесь. 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

ПРОСМОТp >

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

}

Ответ 1

Я просто перечитываю документы, и кажется, что мой общий подход ошибочен. Сначала я должен создать файл в песочнице, а затем переместить его в облако. Другими словами, Apple, кажется, предлагает, чтобы я всегда имел три версии одного и того же файла: один в каталоге моего приложения, один в каталоге демона iCloud моего устройства (который также доступен в автономном режиме) и один в облако:

Приложения используют те же технологии для управления файлами и каталогами в iCloud, которые они выполняют для локальных файлов и каталогов. Файлы и   каталоги в iCloud - это все еще только файлы и каталоги. Ты можешь   открывать их, создавать, перемещать, копировать, читать и писать с   их, удалите их или какие-либо другие операции, которые вы, возможно, захотите   делать. Единственные различия между локальными файлами и каталогами и   Файлы и каталоги iCloud - это URL-адрес, который вы используете для доступа к ним.   Вместо URL-адресов, относящихся к изолированной программной среде приложений, URL-адреса для iCloud   файлы и каталоги относятся к соответствующему iCloud   каталог контейнера.

Чтобы переместить файл или каталог в iCloud:

Создайте файл или каталог локально в изолированной программной среде приложения.. использование файла или каталога должно управляться ведущим файла, например как объект UIDocument.

Использовать URLForUbiquityContainerIdentifier: метод для получения URL-адреса для каталога контейнера iCloud, в котором вы хотите сохранить пункт. Используйте URL-адрес каталога контейнера для создания нового URL-адреса, который определяет местоположение элементов в iCloud. Позвоните setUbiquitous: itemAtURL: destinationURL: ошибка: метод NSFileManager для перемещения элемента в iCloud. Никогда не вызывайте этот метод из своих приложений основная нить; это может блокировать основной поток для расширенного период времени или вызвать тупик с одним из ваших собственных файлов приложений Ведущие. Когда вы перемещаете файл или каталог в iCloud, система копирует этот элемент из песочницы вашего приложения и в частную локальную чтобы он мог отслеживаться демонами iCloud. Даже хотя файл больше не находится в вашей песочнице, ваше приложение все еще имеет полный доступ к нему. Хотя копия файла остается локальной для текущего устройство, файл также отправляется в iCloud, чтобы он мог быть распределен к другим устройствам. Демон iCloud выполняет всю работу по созданию что локальные копии совпадают. Итак, с точки зрения ваше приложение, файл просто находится в iCloud.

Все изменения, внесенные вами в файл или каталог в iCloud, должны быть сделаны используя объект координатора файлов. Эти изменения включают в себя перемещение, удаление, копирование или переименование элемента. Координатор файла обеспечивает что демон iCloud не изменяет файл или каталог на в то же время и гарантирует, что другие заинтересованные стороны будут уведомлены о сделанные вами изменения.

Однако, если вы углубитесь в документы относительно setUbiquitous, вы найдете:

Используйте этот метод для перемещения файла из его текущего местоположения в iCloud. Для файлов, расположенных в изолированной программной среде приложений, , это предполагает физическое удаление файла из каталога песочницы. (Система расширяет ваши привилегии песочницы приложений, чтобы предоставить ей доступ к файлам, которые она перемещает в iCloud.) Вы также можете использовать этот метод для перемещения файлов из iCloud и обратно в локальный каталог.

Итак, это означает, что файл/каталог удаляется из локальной песочницы и перемещается в облако.

Ответ 2

Я использую ваш пример, и мне это нравится, чтобы помочь мне понять основы iCloud. Теперь я борюсь с вашим вопросом за свое приложение, которое должно поддерживать существующих пользователей приложения с локально сохраненным контентом, который может или не может использовать iCloud, создавая эти случаи, насколько я могу судить:

Дела:

  • Новый пользователь
    • имеет icloud - создает документы в icloud
    • no icloud - создавать документы локально
  • Существующий пользователь
    • имеет icloud
      • только что добавлено - перенос локальных документов в icloud
      • не просто добавлен - открыть/сохранить документы в icloud
    • no icloud
      • только что удалено - перенести прежние документы icloud на локальные
      • не просто удален - откройте/сохраните документы на локальный

Если кто-то удалит iCloud - не будут ли вызовы на вездесущий URL-адрес возвращать нуль? Если это так, как мне перенести документы обратно в локальное хранилище? На данный момент я создам пользовательский префикс, но, похоже, немного обходным путем.

Я чувствую, что мне не хватает чего-то очевидного здесь, поэтому, если кто-нибудь может это увидеть, пожалуйста, звоните.

Ответ 3

Если вы хотите, чтобы пользователи могли обмениваться текстовыми сообщениями между устройствами, предшествовавшими iOS 5.0, вам придется делать то, что каждый должен был сделать перед iCloud, и переместить информацию на ваш собственный сервер.

Все, что вам действительно нужно, это сервер, который позволяет вашему приложению сохранять текстовые файлы и связывать их с учетной записью пользователя.

Вам понадобятся пользователи для создания учетной записи, и вам нужно будет управлять процессом самостоятельно, перемещая новую информацию на одном устройстве в свое "облако".

Пользователи будут регистрироваться с той же учетной записью на других устройствах, и вам нужно будет позаботиться о том, чтобы обнаружить, когда другое устройство переместило данные в ваше собственное облако, и обновить текущее устройство с помощью новой информации.

Очевидно, что для устройств iOS 5.0 вы, вероятно, захотите обнаружить измененные файлы для устройств до iOS 5.0 в своем собственном облаке, а также сможете разговаривать с iCloud.

Ответ 4

Не похоже, что вы боретесь с проблемой iCloud/notICloud, а также проблемой iOS5/notIOS5.

Если целью развертывания является iOS5, тогда просто всегда используйте структуру UIDocument. Если это повсеместно, тогда ваш NSMetaDataQuery найдет его в облаке; если он не найдет его на устройстве.

Если, с другой стороны, вы хотите предоставить доступ к вашему представлению до 5.0, вам необходимо будет условно проверить, работает ли работающий iOS 5.0 или выше. Если это тогда, используйте UIDocument; если нет, то прочитайте/напишите данные по-старому.

Мой подход заключался в том, чтобы написать условный метод saveData, который проверяет iOS5. Если он существует, я обновляю счет изменений (или использую менеджер отмены). В вашем случае textViewDidChange вызовет этот метод. Если нет, то он сохраняет на диск старый способ. При загрузке происходит обратное.

Ответ 5

Вас озадачивает "Обработка файлов в iCloud так же, как вы обрабатываете все другие файлы в песочнице вашего приложения". Это справедливо для чего-то вроде Keynote и Numbers, где вы храните кучу файлов, и если у вас iCloud, они начинают синхронизировать магически.

Однако вы строите что-то, что зависит от функциональности iCloud. Вы не можете придерживаться этого утверждения, потому что ваше приложение зависит от iCloud для присутствия для чего-либо, чтобы работать так, как оно предназначалось. Вам нужно либо закрыть приложение, либо просто сказать "пожалуйста, настройте iCloud для этого" или дублируйте функции iCloud (ваши собственные или чужие), которые вы всегда можете использовать независимо.