Основы iCloud и образец кода [закрыто]

85

Как новичок борюсь с iCloud. Есть несколько примеров, но они обычно довольно подробны (на форуме разработчиков есть один для iCloud и CoreData, который является огромным). Документы Apple в порядке, но я все еще не вижу общей картины. Так что терпите меня, некоторые из этих вопросов весьма фундаментальны, но, возможно, на них легко ответить.

Контекст: у меня работает очень простое приложение 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

Документ UID

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

КОНТРОЛЛЕР VIEW

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

}
ничего
источник
4
Я действительно предлагаю разбить это на пару вопросов. Я вижу здесь несколько разных вопросов, и их трудно выделить в стене текста, который у вас здесь. Я бы вернулся к этому вопросу, чтобы просто спросить, что делать людям, у которых не включен iCloud, и разбил бы остальные (только с соответствующими частями вашего образца кода) на отдельные вопросы. Это хорошие вопросы, но я думаю, что их следует разделить.
Брэд Ларсон
@BradLarson Спасибо за ваш комментарий. Прошу прощения, если вопросы немного запутаны, но я думаю, что главный вопрос (как я пытался указать) - это проблема песочницы приложения и песочницы iCloud. Я предоставил полный код (который является самым коротким примером кода iCloud, кстати), так как я думал, что ВЕСЬ контекст жизненно важен для того, чтобы знать, что происходит ... Но я мог бы просто открыть другой вопрос и связать его с этим вопросом, чтобы получить более широкую картину.
n.evermind
@BradLarson Хорошо, я открыл здесь новый вопрос: stackoverflow.com/questions/7798555/…
n.evermind
Для тех, кто все еще пытается разобраться в Core Data и iCloud, попробуйте эту ссылку ossh.com.au/design-and-technology/software-development/…
Дункан Гроенвальд
Не следует закрывать, это на самом деле один из наиболее конструктивных постов, которые я видел в iCloud ..
Эндрю Смит

Ответы:

22

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

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

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

ничего
источник
1
url ссылка не работает ...
ngb
5

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

Кейсы:

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

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

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

Earnshavian
источник
Я должен добавить, что мне интересно, есть ли класс, который обрабатывает эти случаи, поэтому я просто использую его, и мне не нужно беспокоиться о том, где его сохранить.
Earnshavian
Взгляните на developer.apple.com/library/ios/#documentation/DataManagement/…, который дает пример кода, чтобы определить, следует ли что-то помещать в локальную песочницу или в облако.
n.evermind
Спасибо за это. Я видел этот документ, но раньше в моей миссии iCloud, поэтому я забыл код, который он предлагает. Я попытаюсь адаптировать ваш образец для поддержки локального и удаленного доступа. Я все еще не понимаю, как мы обрабатываем пользователя, который отключает iCloud, поскольку мы теряем вездесущий URL-адрес, но у меня есть трещина и поделюсь обновлением.
Earnshavian
1
Так что в каком-то смысле это немного глупо, что мы должны использовать URL-адреса для облака и PATH для локальной песочницы. Было бы хорошо, если бы iCloud мог справиться со всем за нас ... но в этом случае нам в основном нужно кодировать два разных метода для каждого открываемого файла.
n.evermind
Я просто перечитал твой пост. Теперь я сохраняю предпочтения пользователя (т.е. пользователь хочет / не хочет использовать iCloud) в NSUserDefaults. Это то, что предлагает Apple. Я всегда проверяю, доступен ли iCloud. Если он недоступен, я говорю пользователям включить его, но только если они явно не сказали приложению, что не хотят его использовать. В противном случае это будет раздражать тех, кто не хочет использовать iCloud. Как только я определю, включен ли iCloud, я буду либо следовать повсеместному URL-маршруту и ​​использовать UIDocument, либо просто открывать файлы из песочницы, как в старые добрые времена.
n.evermind
4

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

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

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

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

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

Джонатан Уотмоу
источник
Спасибо. Другими словами, если я не хочу поддерживать устройства до iOS 5, я просто использую UIDocument и забываю о содержимом каталога документов в песочнице моего приложения.
n.evermind
В значительной степени, хотя, насколько я могу судить, у вас все еще будет документ в песочнице, который UIDocument поможет вам в посредничестве с iCloud, но вам сообщат, когда вы сможете получить к нему доступ ... Я все еще получаю с этим справляюсь сама!
Джонатан Уотмо,
3

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

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

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

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

Майкл
источник
1

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

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

Джеспер
источник
Спасибо. Поэтому я думаю, мне нужно выбрать, буду ли я делать приложение только для iCloud или какой-то гибрид для людей, которые отключают функциональность iCloud. Поскольку iCloud настолько сложен, я предпочитаю использовать только приложение iCloud. Спасибо.
n.evermind