86 votes

Les bases d'iCloud et un exemple de code

En tant que débutant, j'ai du mal avec iCloud. Il existe quelques exemples, mais ils sont généralement assez détaillés (sur le forum des développeurs, il y en a un pour iCloud et CoreData qui est énorme). Le site documents sur la pomme sont OK, mais je n'arrive toujours pas à voir la vue d'ensemble. Je vous prie donc d'être indulgent avec moi, certaines de ces questions sont assez fondamentales, mais peut-être faciles à répondre.

Le contexte : J'ai une application iCloud très simple en cours d'exécution (exemple de code complet ci-dessous). Il n'y a qu'une seule UITextView affichée à l'utilisateur et sa saisie est enregistrée dans un fichier appelé text.txt.

enter image description here

Le fichier txt est poussé vers le nuage et mis à la disposition de tous les appareils. Cela fonctionne parfaitement, mais :

Principal problème : qu'en est-il des utilisateurs qui n'utilisent pas iCloud ?

Lorsque je lance mon application (voir le code ci-dessous), je vérifie si l'utilisateur a activé iCloud. Si iCloud est activé, tout va bien. L'application va chercher le fichier text.txt dans le nuage. Si elle le trouve, elle le charge et l'affiche à l'utilisateur. Si le fichier text.txt n'est pas trouvé dans le nuage, elle crée simplement un nouveau fichier text.txt et l'affiche à l'utilisateur.

Si l'utilisateur n'a pas activé iCloud, rien ne se passera. Comment faire en sorte que les utilisateurs qui n'ont pas iCloud puissent quand même travailler avec mon application texte ? Ou dois-je simplement les ignorer ? Devrais-je écrire des fonctions distinctes pour les utilisateurs non-iCloud ? Par exemple, des fonctions dans lesquelles je charge simplement un text.txt depuis le dossier des documents ?

Apple écrit :

Traitez les fichiers dans iCloud de la même manière que tous les autres fichiers de votre bac à sable d'applications.

Cependant, dans mon cas, il n'y a plus de sandbox d'application "normale". Il est dans le nuage. Ou dois-je d'abord charger mon text.txt depuis le disque et ensuite vérifier avec iCloud s'il y a quelque chose de plus à jour ?

Problème connexe : Structure des fichiers - Sandbox vs. Cloud

Peut-être que mon principal problème est une incompréhension fondamentale de la façon dont iCloud est censé fonctionner. Lorsque je crée une nouvelle instance d'un UIDocument, je dois écraser deux méthodes. D'abord - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError pour récupérer des fichiers dans le nuage et ensuite -(id)contentsForType:(NSString *)typeName error:(NSError **)outError pour mettre les fichiers dans le nuage.

Dois-je intégrer des fonctions distinctes qui enregistreront également une copie locale de text.txt dans mon bac à sable ? Cela fonctionnera-t-il pour les utilisateurs non-iCloud ? Si je comprends bien, iCloud enregistre automatiquement une copie locale du fichier text.txt. Il ne devrait donc pas être nécessaire que j'enregistre quoi que ce soit dans l'"ancien" bac à sable de mon application (c'est-à-dire tel qu'il était à l'époque, avant iCloud). Actuellement, mon bac à sable est totalement vide, mais je ne sais pas si c'est correct. Devrais-je y conserver une autre copie de text.txt ? J'ai l'impression d'encombrer ma structure de données... car il y a un text.txt dans le nuage, un autre dans le bac à sable iCloud de mon appareil (qui fonctionnera même si je suis hors ligne), et un troisième dans le bon vieux bac à sable de mon application...


MON CODE : Un exemple simple de code iCloud

Ceci est librement basé sur un exemple que j'ai trouvé dans le forum des développeurs et sur la vidéo de la session WWDC. Je l'ai réduit au strict minimum. Je ne suis pas sûr que ma structure MVC soit bonne. Le modèle est dans l'AppDelegate, ce qui n'est pas idéal. Toute suggestion pour l'améliorer est la bienvenue.


EDIT : J'ai essayé d'extraire la question principale et l'ai postée [ici]. 4


VUE D'ENSEMBLE :

Overview

La partie la plus importante qui charge le fichier text.txt depuis le nuage :

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

Le 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

LE CONTRÔLEUR DE VUE

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

}

22voto

n.evermind Points 4182

Je viens de relire la documentation et il semble que mon approche générale soit erronée. Je devrais d'abord créer le fichier dans le bac à sable, puis le déplacer vers le cloud. En d'autres termes, Apple semble suggérer que je devrais avoir trois versions du même fichier à tout moment : une dans le répertoire de mon application, une dans le répertoire de démonstration iCloud de mon appareil (qui est également accessible si hors ligne) et une dans le nuage :

Les applications utilisent les mêmes technologies pour gérer les fichiers et répertoires dans iCloud que pour les fichiers et répertoires locaux. Les fichiers et répertoires dans iCloud ne sont toujours que des fichiers et des répertoires. Vous pouvez les ouvrir, les créer, les déplacer, les copier, les lire et les écrire, les supprimer, etc. lire et écrire à partir d'eux, les supprimer, ou toute autre opération que vous pouvez faire. Les seules différences entre les fichiers et répertoires locaux et les fichiers et répertoires iCloud sont les suivantes fichiers et répertoires iCloud est l'URL que vous utilisez pour y accéder. Au lieu que les URL soient relatives au bac à sable de votre application, les URL des fichiers et répertoires iCloud sont relatives au répertoire correspondant du conteneur iCloud correspondant.

Pour déplacer un fichier ou un répertoire vers iCloud :

Créez le fichier ou le répertoire localement dans le bac à sable de votre application. Pendant qu'il est l'utilisation, le fichier ou le répertoire doit être géré par un présentateur de fichier, tel qu'un objet UIDocument. comme un objet UIDocument.

Utilisez la méthode URLForUbiquityContainerIdentifier : pour récupérer une URL pour le répertoire du conteneur iCloud dans lequel vous souhaitez stocker l'élément. élément. Utilisez l'URL du répertoire de conteneurs pour créer une nouvelle URL qui spécifie l'emplacement de l'élément dans iCloud. Appelez la commande méthode setUbiquitous:itemAtURL:destinationURL:error : de NSFileManager pour déplacer l'élément vers iCloud. N'appelez jamais cette méthode depuis le thread principal de votre application. de votre application, car cela pourrait bloquer votre thread principal pendant une période de temps prolongée ou provoquer un blocage avec l'un des présentateurs de fichiers présentateurs. Lorsque vous déplacez un fichier ou un répertoire vers iCloud, le système copie cet élément hors du bac à sable de votre application et le place dans un répertoire privé local afin qu'il puisse être surveillé par le démon iCloud. Même Même si le fichier n'est plus dans votre sandbox, votre application y a toujours accès complet à ce fichier. Bien qu'une copie du fichier reste locale sur l'appareil actuel, le fichier est également envoyé à l'appareil de destination. l'appareil actuel, le fichier est également envoyé à iCloud afin qu'il puisse être distribué à d'autres appareils. Le démon iCloud s'occupe de tout le travail pour s'assurer s'assurer que les copies locales sont les mêmes. Ainsi, du point de vue de votre application, le fichier est simplement dans iCloud.

Toutes les modifications que vous apportez à un fichier ou un répertoire dans iCloud doivent être effectuées en utilisant un objet coordinateur de fichiers. Ces modifications incluent le déplacement, la suppression, la copie ou le renommage de l'élément. Le coordinateur de fichiers garantit que le démon iCloud ne modifie pas le fichier ou le répertoire en même temps et même temps et veille à ce que les autres parties intéressées soient informées des les changements que vous effectuez.

Cependant, si vous creusez un peu plus profondément dans les documents concernant setUbiquitous, vous trouverez :

Utilisez cette méthode pour déplacer un fichier de son emplacement actuel vers iCloud. Pour les fichiers situés dans le bac à sable d'une application, cela implique de retirer physiquement le fichier du répertoire sandbox . (Le système étend les privilèges de sandbox de votre application pour lui donner accès aux fichiers qu'elle déplace vers iCloud). Vous pouvez également utiliser cette méthode pour déplacer des fichiers hors d'iCloud et les replacer dans un répertoire local.

Cela semble donc signifier qu'un fichier/répertoire est supprimé du sandbox local et déplacé dans le cloud.

5voto

earnshavian Points 743

J'ai utilisé votre exemple et je l'apprécie pour m'aider à comprendre les bases d'iCloud. Maintenant, je me débats avec votre question pour ma propre application qui doit prendre en charge les utilisateurs existants de l'application avec du contenu stocké localement, qui peuvent ou non utiliser iCloud pour créer ces cas, pour autant que je sache :

Cas :

  1. Nouvel utilisateur
    • a icloud - créer des documents dans icloud
    • pas d'icloud - créer des documents localement
  2. Utilisateur existant
    • a icloud
      • juste ajouté - migrer les documents locaux vers icloud
      • pas seulement ajouté - ouvrir/enregistrer les documents dans icloud
    • pas d'icloud
      • juste supprimé - migrer les anciens documents icloud vers le local
      • pas seulement supprimé - ouvrir/enregistrer les documents dans le local

Si quelqu'un supprime iCloud, les appels à l'URL omniprésente ne renverraient-ils pas un résultat nul ? Si c'est le cas, comment puis-je migrer les documents vers le stockage local ? Je vais créer un préfixe utilisateur pour l'instant, mais cela me semble être un peu une solution de rechange.

J'ai l'impression de passer à côté de quelque chose d'évident, alors si quelqu'un le voit, merci de m'en faire part.

4voto

Jonathan Watmough Points 744

Si vous voulez que les utilisateurs puissent partager du texte entre des appareils qui sont antérieurs à iOS 5.0, vous allez devoir faire ce que tout le monde devait faire avant iCloud et déplacer les informations sur votre propre serveur.

Tout ce dont vous avez besoin, c'est d'un serveur quelque part qui permette à votre application d'enregistrer ses fichiers texte et de les associer à un compte utilisateur.

Les utilisateurs devront créer un compte et vous devrez gérer vous-même le processus consistant à transférer les nouvelles informations d'un appareil vers votre propre "nuage".

Les utilisateurs s'enregistreront avec le même compte sur d'autres appareils et vous devrez vous occuper de détecter quand un autre appareil a déplacé des données sur votre propre cloud, et mettre à jour l'appareil actuel avec les nouvelles informations.

Évidemment, pour les appareils iOS 5.0, vous voudrez probablement détecter les fichiers modifiés pour les appareils pré-iOS 5.0 dans votre propre nuage, et aussi être capable de communiquer avec iCloud.

3voto

Michael Points 249

Il ne semble pas que vous soyez confronté à un problème iCloud/non iCloud, mais plutôt à un problème iOS5/non iOS5.

Si votre cible de déploiement est iOS5, il suffit de toujours utiliser la structure UIDocument. Si elle est omniprésente, alors votre NSMetaDataQuery la trouvera dans le nuage ; sinon, elle la trouvera sur l'appareil.

Si, par contre, vous voulez fournir un accès à votre application avant la version 5.0, vous devrez vérifier de manière conditionnelle si l'iOS en cours d'exécution est 5.0 ou plus. Si c'est le cas, utilisez UIDocument ; sinon, lisez/écrivez les données à l'ancienne.

Mon approche a consisté à écrire une méthode conditionnelle saveData qui vérifie la présence d'iOS5. S'il existe, je mets à jour le nombre de modifications (ou j'utilise un gestionnaire d'annulation). Dans votre cas, le textViewDidChange appelle cette méthode. Si ce n'est pas le cas, la sauvegarde sur le disque s'effectue selon l'ancienne méthode. Au chargement, c'est le contraire qui se produit.

1voto

Jesper Points 3116

Vous êtes déconcerté par "Traitez les fichiers dans iCloud de la même manière que vous traitez tous les autres fichiers dans votre bac à sable d'application". Cela est vrai pour quelque chose comme Keynote et Numbers où vous conservez un tas de fichiers, et si vous avez iCloud, ils commencent à se synchroniser comme par magie.

Cependant, vous construisez quelque chose qui dépend d'une fonctionnalité de type iCloud. Vous ne pouvez pas vous en tenir à cette affirmation car votre application dépend de la présence d'iCloud pour que tout fonctionne comme il se doit. Vous devrez soit fermer votre application et dire simplement "veuillez configurer iCloud pour que cela fonctionne", soit dupliquer une fonctionnalité de type iCloud (la vôtre ou celle de quelqu'un d'autre) que vous pourrez toujours utiliser, quoi qu'il arrive.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X