Conceptos básicos de iCloud y muestra de código [cerrado]

85

Como principiante, estoy luchando con iCloud. Hay algunas muestras, pero generalmente son bastante detalladas (en el foro de desarrolladores hay una para iCloud y CoreData que es masiva). Los documentos de Apple están bien, pero todavía no puedo ver el panorama general. Así que por favor tengan paciencia conmigo, algunas de estas preguntas son bastante fundamentales, pero posiblemente fáciles de responder.

Contexto: Tengo una aplicación iCloud muy simple en ejecución (código de muestra completo a continuación). Solo se muestra un UITextView al usuario y su entrada se guarda en un archivo llamado text.txt.

ingrese la descripción de la imagen aquí

El archivo txt se envía a la nube y está disponible para todos los dispositivos. Funciona perfectamente, pero:

Problema principal: ¿Qué pasa con los usuarios que no usan iCloud?

Cuando ejecuto mi aplicación (ver código a continuación), verifico si el usuario tiene iCloud habilitado. Si iCloud está habilitado, todo está bien. La aplicación sigue adelante y busca text.txt en la nube. Si lo encuentra, lo cargará y se lo mostrará al usuario. Si no se encuentra text.txt en la nube, simplemente creará un nuevo text.txt y se lo mostrará al usuario.

Si el usuario no tiene iCloud habilitado, no pasará nada. ¿Cómo haré posible que los usuarios que no son de iCloud puedan seguir trabajando con mi aplicación de texto? ¿O simplemente los ignoro? ¿Necesitaría escribir funciones separadas para usuarios que no son de iCloud? Es decir, funciones en las que simplemente cargo un text.txt desde la carpeta de documentos.

Apple escribe :

Trate los archivos en iCloud de la misma manera que trata todos los demás archivos en la zona de pruebas de su aplicación.

Sin embargo, en mi caso ya no existe una zona de pruebas de aplicaciones "normal". Está en la nube. ¿O siempre cargo primero mi text.txt desde el disco y luego verifico con iCloud si hay algo más actualizado?

Problema relacionado: Estructura de archivos - Sandbox vs.Nube

Quizás mi principal problema es un malentendido fundamental de cómo se supone que funciona iCloud. Cuando creo una nueva instancia de un UIDocument, tendré que sobrescribir dos métodos. Primero - (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outErrorpara obtener archivos de la nube y luego -(id)contentsForType:(NSString *)typeName error:(NSError **)outErrorpara llevarlos a la nube.

¿Tengo que incorporar funciones separadas que también guardarán una copia local de text.txt en mi caja de arena? ¿Funcionará esto para usuarios que no son de iCloud? Según tengo entendido, iCloud guardará una copia local de text.txt automáticamente. Por lo tanto, no debería ser necesario que guarde nada en la caja de arena 'antigua' de mi aplicación (es decir, como solía ser en los días anteriores a iCloud). En este momento, mi caja de arena está totalmente vacía, pero no sé si esto es correcto. ¿Debo guardar otra copia de text.txt allí? Esto se siente como saturar mi estructura de datos ... ya que hay un text.txt en la nube, uno en la caja de arena de iCloud en mi dispositivo (que funcionará incluso si estoy desconectado) y una tercera en la vieja caja de arena de mi aplicación ...


MI CÓDIGO: un código de muestra simple de iCloud

Esto está vagamente basado en un ejemplo que encontré en el foro de desarrolladores y en el video de la sesión de la WWDC. Lo reduje al mínimo. No estoy seguro de que mi estructura MVC sea buena. El modelo está en AppDelegate, lo que no es ideal. Cualquier sugerencia para mejorarlo es bienvenida.


EDITAR: Traté de extraer la pregunta principal y la publiqué [aquí]. 4


VISIÓN DE CONJUNTO:

Visión general

El bit más importante que carga el text.txt desde la nube:

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

El 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

EL CONTROLADOR DE VISTA

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

}
no importa
fuente
4
Realmente sugiero dividir esto en un par de preguntas. Veo algunas preguntas diferentes enterradas aquí, y es difícil distinguirlas en la pared de texto que tiene aquí. Volvería esta pregunta a simplemente preguntar qué hacer para las personas que no tienen iCloud habilitado, y dividiría las otras (con solo las partes relevantes de su código de muestra) en preguntas separadas. Son buenas preguntas, pero creo que deberían separarse.
Brad Larson
@BradLarson Gracias por tu comentario. Lo siento si las preguntas están un poco confusas, pero creo que la pregunta principal (como intenté indicar) es el problema de la caja de arena de la aplicación frente a la caja de arena de iCloud. Proporcioné el código completo (que es el ejemplo de código de iCloud más corto, por cierto) porque pensé que TODO el contexto es vital para saber qué está pasando ... Pero podría abrir otra pregunta y vincularla a esta pregunta para Obtenga una imagen más amplia.
n
@BradLarson OK, abrí una nueva pregunta aquí: stackoverflow.com/questions/7798555/…
n.evermind
Para aquellos que todavía están tratando de manejar Core Data e iCloud, prueben este enlace ossh.com.au/design-and-technology/software-development/…
Duncan Groenewald
No debería cerrarse, esta es en realidad una de las publicaciones más constructivas que he visto en iCloud ..
Andrew Smith

Respuestas:

22

Acabo de volver a leer los documentos y parece que mi enfoque general es incorrecto. Primero debería crear el archivo en la caja de arena y luego moverlo a la nube. En otras palabras, Apple parece sugerir que debería tener tres versiones del mismo archivo en todo momento: una en el directorio de mi aplicación, una en el directorio demonio de iCloud de mi dispositivo (que también es accesible si está desconectado) y una en la nube:

Las aplicaciones utilizan las mismas tecnologías para administrar archivos y directorios en iCloud que para los archivos y directorios locales. Los archivos y directorios en iCloud siguen siendo solo archivos y directorios. Puede abrirlos, crearlos, moverlos, copiarlos, leerlos y escribir en ellos, eliminarlos o cualquiera de las otras operaciones que desee realizar. Las únicas diferencias entre los archivos y directorios locales y los archivos y directorios de iCloud es la URL que usa para acceder a ellos. En lugar de que las URL sean relativas a la zona de pruebas de su aplicación, las URL de los archivos y directorios de iCloud son relativas al directorio contenedor de iCloud correspondiente.

Para mover un archivo o directorio a iCloud:

Cree el archivo o directorio localmente en la zona de pruebas de su aplicación.Mientras está en uso, el archivo o directorio debe ser administrado por un presentador de archivos, como un objeto UIDocument.

Utilice el método URLForUbiquityContainerIdentifier: para recuperar una URL para el directorio del contenedor de iCloud en el que desea almacenar el elemento. Utilice la URL del directorio del contenedor para crear una nueva URL que especifique la ubicación del elemento en iCloud. Llame al método setUbiquitous: itemAtURL: destinationURL: error: de NSFileManager para mover el elemento a iCloud. Nunca llame a este método desde el hilo principal de su aplicación; Si lo hace, podría bloquear su hilo principal durante un período prolongado de tiempo o causar un bloqueo con uno de los presentadores de archivos de su propia aplicación. Cuando mueves un archivo o directorio a iCloud, el sistema copia ese elemento de la zona de pruebas de tu aplicación y lo coloca en un directorio local privado para que pueda ser monitoreado por el demonio de iCloud. A pesar de que el archivo ya no está en su caja de arena, su aplicación aún tiene acceso completo a él. Aunque una copia del archivo permanece local en el dispositivo actual, el archivo también se envía a iCloud para que pueda distribuirse a otros dispositivos. El demonio de iCloud maneja todo el trabajo de asegurarse de que las copias locales sean las mismas. Entonces, desde la perspectiva de su aplicación, el archivo solo está en iCloud.

Todos los cambios que realice en un archivo o directorio en iCloud deben realizarse utilizando un objeto coordinador de archivos. Estos cambios incluyen mover, eliminar, copiar o cambiar el nombre del elemento. El coordinador de archivos se asegura de que el demonio de iCloud no cambie el archivo o directorio al mismo tiempo y asegura que otras partes interesadas sean notificadas de los cambios que realice.

Sin embargo, si profundiza un poco más en los documentos relacionados con setUbiquitous, encontrará:

Utilice este método para mover un archivo desde su ubicación actual a iCloud. Para los archivos ubicados en la zona de pruebas de una aplicación, esto implica eliminar físicamente el archivo del directorio de la zona de pruebas . (El sistema extiende los privilegios de la zona de pruebas de su aplicación para darle acceso a los archivos que mueve a iCloud). También puede usar este método para mover archivos fuera de iCloud y volver a colocarlos en un directorio local.

Entonces, esto parece significar que un archivo / directorio se elimina del entorno de pruebas local y se mueve a la nube.

no importa
fuente
1
enlace URL roto ...
ngb
5

He estado usando tu ejemplo y me gusta porque me ayuda a comprender los conceptos básicos de iCloud. Ahora estoy discutiendo con su pregunta para mi propia aplicación, que tiene que admitir a los usuarios existentes de la aplicación con contenido almacenado localmente que pueden o no usar iCloud creando estos casos, por lo que puedo decir:

Casos:

  1. Nuevo Usuario
    • tiene icloud - crea documentos en icloud
    • no icloud: crea documentos localmente
  2. Usuario existente
    • tiene icloud
      • recién agregado - migrar documentos locales a icloud
      • no solo agregado: abra / guarde documentos en icloud
    • no icloud
      • recién eliminado: migre los documentos de icloud anteriores a local
      • no solo eliminado: abre / guarda documentos en local

Si alguien elimina iCloud, ¿las llamadas a la URL ubicua no devolverían nada? Si ese es el caso, ¿cómo puedo migrar los documentos al almacenamiento local? Crearé una preferencia de usuario por ahora, pero parece una pequeña solución.

Siento que me estoy perdiendo algo obvio aquí, así que si alguien puede verlo, por favor intervenga.

Earnshavian
fuente
Debo agregar que me pregunto si hay una clase que maneje estos casos, así que solo uso eso y no tengo que preocuparme por dónde guardarlo.
Earnshavian
Eche un vistazo a developer.apple.com/library/ios/#documentation/DataManagement/… que proporciona un código de muestra para determinar si algo debe colocarse en la caja de arena local o en la nube.
n
Gracias por eso. Había visto ese documento, pero antes en mi misión de iCloud, había olvidado el código que ofrece. Intentaré adaptar su muestra para que sea compatible con local y remoto. Todavía no tengo claro cómo manejamos al usuario que deshabilita iCloud ya que perdemos la URL ubicua, pero lo intentaré y compartiré una actualización.
Earnshavian
1
Entonces, en cierto modo, es un poco estúpido que tengamos que usar URL para la nube y PATH para la zona de pruebas local. Sería bueno si iCloud pudiera manejar todo por nosotros ... pero de esta manera, básicamente necesitamos codificar dos métodos diferentes para cada archivo que abrimos.
n
Acabo de volver a leer tu publicación. Ahora estoy guardando la preferencia del usuario (es decir, el usuario quiere / no quiere usar iCloud) en NSUserDefaults. Esto es lo que también sugiere Apple. Siempre compruebo si se puede acceder a iCloud. Si no es accesible, les digo a los usuarios que lo enciendan, pero solo si no le han dicho explícitamente a la aplicación que no desean usarla. De lo contrario, se vuelve molesto para aquellos que no desean usar iCloud. Una vez que haya determinado si iCloud está habilitado, seguiré la ruta de la URL ubicua y usaré UIDocument O simplemente abriré los archivos desde la caja de arena como en los viejos tiempos.
n
4

Si desea que los usuarios puedan compartir texto entre dispositivos que son anteriores a iOS 5.0, tendrá que hacer lo que todos tenían que hacer antes de iCloud y mover la información a su propio servidor.

Todo lo que realmente necesita es un servidor en algún lugar que le permita a su aplicación guardar sus archivos de texto y asociarlos con una cuenta de usuario.

Necesitará que los usuarios creen una cuenta y deberá administrar el proceso usted mismo, de mover nueva información en un dispositivo a su propia 'nube'.

Los usuarios se registrarán con la misma cuenta en otros dispositivos y deberá encargarse de detectar cuándo otro dispositivo ha movido datos a su propia nube y actualizar el dispositivo actual con la nueva información.

Obviamente, para los dispositivos iOS 5.0, probablemente querrá detectar archivos modificados para dispositivos anteriores a iOS 5.0 en su propia nube, y también poder hablar con iCloud.

Jonathan Watmough
fuente
Gracias. Entonces, en otras palabras, si no quiero admitir dispositivos anteriores a iOS 5, simplemente uso UIDocument y me olvido del contenido del directorio doc en la caja de arena de mi aplicación.
n
Básicamente, aunque por lo que puedo decir, todavía tendrá un documento en la caja de arena que UIDocument ayudará a mediar con iCloud por usted, pero se le dirá cuándo puede acceder a él ... para lidiar con esto yo mismo!
Jonathan Watmough
3

No parece que esté luchando con un problema de iCloud / notICloud tanto como con un problema de iOS5 / notIOS5.

Si su destino de implementación es iOS5, simplemente use siempre la estructura UIDocument. Si es ubicuo, entonces su NSMetaDataQuery lo encontrará en la nube; si no, lo encontrará en el dispositivo.

Si, por otro lado, desea proporcionar acceso anterior a la 5.0 a su aplicación, entonces deberá verificar condicionalmente si el iOS en ejecución es 5.0 o superior. Si es así, utilice UIDocument; si no, lea / escriba los datos de la manera anterior.

Mi enfoque fue escribir un método saveData condicional que verifique iOS5. Si existe, actualizo el recuento de cambios (o uso un administrador de deshacer). En su caso, textViewDidChange llamaría a este método. Si no es así, se guarda en el disco de la forma anterior. Al cargar, ocurre lo contrario.

Miguel
fuente
1

Está desconcertado por "Trate los archivos en iCloud de la misma manera que trata todos los demás archivos en la zona de pruebas de su aplicación". Esto es válido para algo como Keynote y Numbers, donde guardas un montón de archivos, y si tienes iCloud, comienzan a sincronizarse mágicamente.

Sin embargo, está creando algo que depende de la funcionalidad similar a iCloud. No puede aferrarse a esa declaración porque su aplicación depende de que iCloud esté presente para que cualquier cosa funcione de la manera que debe. Tendrá que cerrar su aplicación y simplemente decir "por favor configure iCloud para que esto funcione" o duplicar la funcionalidad similar a iCloud (la suya o la de otra persona) que siempre puede usar, independientemente.

Jesper
fuente
Gracias. Así que supongo que tengo que elegir si hago una aplicación solo para iCloud o algún tipo de híbrido para las personas que abandonan la funcionalidad de iCloud. Como iCloud es tan complejo, tiendo a optar por una aplicación exclusiva de iCloud. Gracias.
n