iphone - verificacion - Conceptos básicos de iCloud y ejemplo de código
itunes (5)
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 iCloud de mi dispositivo (que también es accesible si está fuera de línea) y una en la nube:
Las aplicaciones utilizan las mismas tecnologías para administrar los archivos y directorios en iCloud que para los archivos y directorios locales. Los archivos y directorios en iCloud todavía son solo archivos y directorios. Puede abrirlos, crearlos, moverlos, copiarlos, leerlos y escribirlos, eliminarlos o cualquier otra operación que desee realizar. Las únicas diferencias entre los archivos y directorios locales y los archivos y directorios de iCloud es la URL que utiliza para acceder a ellos. En lugar de que las URL sean relativas a la zona de pruebas de su aplicación, las URL para los archivos y directorios de iCloud son relativas al directorio de contenedores de iCloud correspondiente.
Para mover un archivo o directorio a iCloud:
Cree el archivo o directorio localmente en el arenero de su aplicación. Mientras está en uso, el archivo o directorio debe ser administrado por un presentador de archivo, como un objeto UIDocument.
Utilice el método URLForUbiquityContainerIdentifier: para recuperar una URL para el directorio de contenedores de iCloud en el que desea almacenar el elemento. Use la URL del directorio contenedor para crear una nueva URL que especifique la ubicación del elemento en iCloud. Llame al setUbiquitous: itemAtURL: destinationURL: error: método 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 de tiempo prolongado o provocar un punto muerto con uno de los presentadores de archivos de su propia aplicación. Cuando mueve un archivo o directorio a iCloud, el sistema copia ese elemento fuera de la zona de pruebas de su aplicación y en un directorio local privado para que pueda ser monitoreado por el daemon de iCloud. Aunque el archivo ya no está en su entorno limitado, su aplicación todavía 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 se pueda distribuir a otros dispositivos. El daemon iCloud se encarga de 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 renombrar el artículo. El coordinador de archivos garantiza que el daemon de iCloud no cambie el archivo o directorio al mismo tiempo y garantiza que se notifique a otras partes interesadas de los cambios que realice.
Sin embargo, si profundizas un poco más en los documentos relacionados con setUbiquitous, encontrarás:
Use 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 caja de arena . (El sistema amplía los privilegios de espacio aislado 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 un directorio local.
Esto parece significar que un archivo / directorio se borra del sandbox local y se mueve a la nube.
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. Por favor, tengan paciencia, algunas de estas preguntas son fundamentales, pero posiblemente sean 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 una UITextView al usuario y su entrada se guarda en un archivo llamado text.txt.
El archivo txt se envía a la nube y se pone a disposición de todos los dispositivos. Funciona perfectamente, pero:
Problema principal: ¿qué pasa con los usuarios que no usan iCloud?
Cuando lance mi aplicación (vea el código a continuación), verifico si el usuario tiene habilitada iCloud. Si iCloud está habilitado, todo está bien. La aplicación sigue adelante y busca texto.txt en la nube. Si se encuentra, lo cargará y lo mostrará al usuario. Si text.txt no se encuentra en la nube, simplemente creará un nuevo text.txt y lo mostrará al usuario.
Si el usuario no tiene habilitado iCloud, no pasará nada. ¿Cómo puedo hacer posible que los usuarios que no son iCloud aún puedan trabajar con mi aplicación de texto? ¿O simplemente los ignoro? ¿Tendría que escribir funciones separadas para usuarios que no sean iCloud? Es decir, funciones en las que simplemente carga un texto.txt desde la carpeta de documentos.
Trate los archivos en iCloud de la misma manera en que trata a todos los demás archivos en el entorno limitado de la aplicación.
Sin embargo, en mi caso ya no existe un entorno de aplicaciones ''normal''. Está en la nube. ¿O siempre cargo primero mi texto.txt desde el disco y luego compruebo con iCloud si hay algo más actualizado?
Problema relacionado: Estructura de archivos: Sandbox vs. Cloud
Tal vez mi problema principal es un malentendido fundamental sobre 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 **)outError
para obtener archivos de la nube y luego -(id)contentsForType:(NSString *)typeName error:(NSError **)outError
para obtener archivos en la nube.
¿Debo incorporar funciones separadas que también guarden una copia local de text.txt en mi sandbox? ¿Esto funcionará para usuarios que no sean iCloud? Como entiendo iCloud, guardará una copia local de text.txt automáticamente. Por lo tanto, no debería haber ninguna necesidad de guardar nada en el "viejo" entorno limitado de mi aplicación (es decir, como solía ser en los viejos días anteriores a iCloud). En este momento, mi sandbox está totalmente vacío, pero no sé si esto es correcto. ¿Debo guardar otra copia de text.txt ahí? Esto parece abarrotar mi estructura de datos ... ya que hay un texto.txt en la nube, uno en el entorno limitado de iCloud en mi dispositivo (que funcionará incluso si no estoy conectado) y un tercero en el viejo y aceptable entorno limitado de mi aplicación ...
MI CÓDIGO: Un código de muestra simple de iCloud
Esto se basa libremente en un ejemplo que encontré en el foro de desarrolladores y en el video de la sesión de WWDC. Lo reduje al mínimo. No estoy seguro de que mi estructura MVC sea buena. El modelo está en AppDelegate que no es ideal. Cualquier sugerencia para mejorarla es bienvenida.
EDITAR: Intenté extraer la pregunta principal y la publiqué [aquí]. 4
VISIÓN DE CONJUNTO:
El bit más importante que carga el texto.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
THE VIEWCONTROLLER
//
// 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];
}
He estado usando tu ejemplo y me gusta para ayudarme a comprender los conceptos básicos de iCloud. Ahora estoy discutiendo con su pregunta para mi propia aplicación que tiene que apoyar a los usuarios existentes de la aplicación con contenido almacenado localmente que pueden o no estar usando iCloud creando estos casos por lo que yo sé:
Casos:
- Nuevo usuario
- tiene icloud - crea documentos en icloud
- no icloud: crea documentos localmente
- Usuario existente
- tiene icloud
- recién agregado: migre los documentos locales a icloud
- no solo agregado: abre / guarda documentos en icloud
- no icloud
- recién eliminado: migre los documentos anteriores de icloud a locales
- no solo eliminado: abre / guarda documentos en local
- tiene icloud
Si alguien elimina iCloud, ¿las llamadas a la URL ubicua no devolverían? Si ese es el caso, ¿cómo migro los documentos al almacenamiento local? Crearé un usuario pref por ahora, pero parece una solución temporal.
Siento que me falta algo obvio aquí, así que si alguien puede verlo, por favor toque.
Parece que no estás luchando con un problema de iCloud / notICloud tanto como un problema de iOS5 / notIOS5.
Si su destino de despliegue es iOS5, simplemente utilice siempre la estructura UIDocument. Si es omnipresente, su NSMetaDataQuery lo encontrará en la nube; si no, lo encontrará en el dispositivo.
Si, por otro lado, desea proporcionar acceso pre 5.0 a su aplicación, entonces deberá verificar condicionalmente si el iOS que corre es 5.0 o superior. Si es así, use UIDocument; si no, entonces leer / escribir datos de la manera antigua.
Mi enfoque era escribir un método de guardar datos condicional que busca iOS5. Si existe, actualizo el recuento de cambios (o uso un administrador de deshacer). En su caso, textViewDidChange llamaría a este método. De lo contrario, se guarda en el disco de la manera antigua. Al cargar, sucede lo contrario.
Si desea que los usuarios puedan compartir texto entre dispositivos 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 alguna parte que permita que su aplicación guarde sus archivos de texto y los asocie con una cuenta de usuario.
Necesitará que los usuarios creen una cuenta y usted tendrá que 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 usted tendrá que ocuparse de detectar cuándo otro dispositivo ha movido los datos a su propia nube, y actualizar el dispositivo actual con la nueva información.
Obviamente, para dispositivos con iOS 5.0, probablemente desee detectar archivos modificados para dispositivos anteriores a iOS 5.0 en su propia nube, y también podrá hablar con iCloud.
Usted está confundido por "Tratar archivos en iCloud de la misma manera que trata el resto de los archivos en el entorno limitado 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ás creando algo que depende de la funcionalidad similar a iCloud. No puede conservar esa afirmación porque su aplicación depende de que iCloud esté presente para que todo funcione como debe. Deberá cerrar su aplicación y simplemente decir "configure iCloud para que esto funcione" o duplicar la funcionalidad similar a iCloud (la suya propia o la de otra persona) que siempre puede usar, independientemente.