2012-03-18 14 views
11

Tengo soporte básico de iCloud en mi aplicación (sincronización de cambios, ubicuidad, etc.), pero una omisión crucial hasta el momento ha sido la falta de soporte de "descarga" para archivos que existen (o tienen cambios) en la nube, pero no están sincronizados con lo que está actualmente en el disco.Método para descargar archivos iCloud? ¿Muy confuso?

que añaden los siguientes métodos para mi aplicación, en base a un código proporcionado por Apple, con un par de retoques:

Los métodos de descarga:

- (BOOL)downloadFileIfNotAvailable:(NSURL*)file { 
    NSNumber* isIniCloud = nil; 

    if ([file getResourceValue:&isIniCloud forKey:NSURLIsUbiquitousItemKey error:nil]) { 
     // If the item is in iCloud, see if it is downloaded. 
     if ([isIniCloud boolValue]) { 
      NSNumber* isDownloaded = nil; 
      if ([file getResourceValue:&isDownloaded forKey:NSURLUbiquitousItemIsDownloadedKey error:nil]) { 
       if ([isDownloaded boolValue]) 
        return YES; 

       // Download the file. 
       NSFileManager* fm = [NSFileManager defaultManager]; 
       NSError *downloadError = nil; 
       [fm startDownloadingUbiquitousItemAtURL:file error:&downloadError]; 
       if (downloadError) { 
        NSLog(@"Error occurred starting download: %@", downloadError); 
       } 
       return NO; 
      } 
     } 
    } 

    // Return YES as long as an explicit download was not started. 
    return YES; 
} 

- (void)waitForDownloadThenLoad:(NSURL *)file { 
    NSLog(@"Waiting for file to download..."); 
    id<ApplicationDelegate> appDelegate = [DataLoader applicationDelegate]; 
    while (true) { 
     NSDictionary *fileAttribs = [[NSFileManager defaultManager] attributesOfItemAtPath:[file path] error:nil]; 
     NSNumber *size = [fileAttribs objectForKey:NSFileSize]; 

     [NSThread sleepForTimeInterval:0.1]; 
     NSNumber* isDownloading = nil; 
     if ([file getResourceValue:&isDownloading forKey:NSURLUbiquitousItemIsDownloadingKey error:nil]) { 
      NSLog(@"iCloud download is moving: %d, size is %@", [isDownloading boolValue], size); 
     } 

     NSNumber* isDownloaded = nil; 
     if ([file getResourceValue:&isDownloaded forKey:NSURLUbiquitousItemIsDownloadedKey error:nil]) { 
      NSLog(@"iCloud download has finished: %d", [isDownloaded boolValue]); 
      if ([isDownloaded boolValue]) { 
       [self dispatchLoadToAppDelegate:file]; 
       return; 
      } 
     } 

     NSNumber *downloadPercentage = nil; 
     if ([file getResourceValue:&downloadPercentage forKey:NSURLUbiquitousItemPercentDownloadedKey error:nil]) { 
      double percentage = [downloadPercentage doubleValue]; 
      NSLog(@"Download percentage is %f", percentage); 
      [appDelegate updateLoadingStatusString:[NSString stringWithFormat:@"Downloading from iCloud (%2.2f%%)", percentage]]; 
     } 
    } 
} 

y el código que comienza/comprueba las descargas :

if ([self downloadFileIfNotAvailable:urlToUse]) { 
      // The file is already available. Load. 
      [self dispatchLoadToAppDelegate:[urlToUse autorelease]]; 
     } else { 
      // The file is downloading. Wait for it. 
      [self performSelector:@selector(waitForDownloadThenLoad:) withObject:[urlToUse autorelease] afterDelay:0]; 
     } 

Por lo que yo puedo decir el código anterior parece muy bien, pero cuando hago un gran número de Chang ES En el dispositivo A, salvar a los cambios, a continuación, abrir Device B (para provocar una descarga en el dispositivo B) esto es lo que veo en la consola:

2012-03-18 12:45:55.858 MyApp[12363:707] Waiting for file to download... 
2012-03-18 12:45:58.041 MyApp[12363:707] iCloud download is moving: 0, size is 101575 
2012-03-18 12:45:58.041 MyApp[12363:707] iCloud download has finished: 0 
2012-03-18 12:45:58.041 MyApp[12363:707] Download percentage is 0.000000 
2012-03-18 12:45:58.143 MyApp[12363:707] iCloud download is moving: 0, size is 101575 
2012-03-18 12:45:58.143 MyApp[12363:707] iCloud download has finished: 0 
2012-03-18 12:45:58.144 MyApp[12363:707] Download percentage is 0.000000 
2012-03-18 12:45:58.246 MyApp[12363:707] iCloud download is moving: 0, size is 101575 
2012-03-18 12:45:58.246 MyApp[12363:707] iCloud download has finished: 0 
2012-03-18 12:45:58.246 MyApp[12363:707] Download percentage is 0.000000 
2012-03-18 12:45:58.347 MyApp[12363:707] iCloud download is moving: 0, size is 177127 
2012-03-18 12:45:58.347 MyApp[12363:707] iCloud download has finished: 0 
2012-03-18 12:45:58.347 MyApp[12363:707] Download percentage is 0.000000 
2012-03-18 12:45:58.449 MyApp[12363:707] iCloud download is moving: 0, size is 177127 
2012-03-18 12:45:58.449 MyApp[12363:707] iCloud download has finished: 0 
2012-03-18 12:45:58.450 MyApp[12363:707] Download percentage is 0.000000 

Así, por la razón que sea:

  1. El la descarga para el archivo comienza sin error
  2. Los atributos del archivo para el estado de descarga del archivo siempre devuelven que no está descargando, no ha terminado de descargarse y el progreso es del 0 por ciento.
  3. Estoy atascado en el ciclo para siempre, aunque el tamaño del archivo cambia entre las comprobaciones.

¿Qué estoy haciendo mal?

Respuesta

6

Años después Todavía estoy experimentando problemas periódicos (aunque mucho más raros después de algunas de las soluciones en otras respuestas) para descargar archivos. Entonces, me puse en contacto con los desarrolladores de Apple para solicitar una revisión/discusión técnica y esto es lo que encontré.

Comprobar periódicamente el estado de descarga del mismo NSURL, incluso si lo está recreando, no es el método preferido para comprobar el estado. No sé por qué no lo está, parece que debería funcionar, pero no es así. En cambio, una vez que comience a descargar el archivo, debe registrar un observador con NSNotificationCenter para el progreso de esa descarga, manteniendo una referencia a la consulta para ese archivo. Aquí está el ejemplo exacto del código que me fue proporcionado. Lo he implementado (con algunos ajustes específicos de la aplicación) en mi aplicación, y parece estar funcionando mucho mejor.

- (void)download:(NSURL *)url 
{ 
    dispatch_queue_t q_default; 
    q_default = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    dispatch_async(q_default, ^{ 

     NSError *error = nil; 
     BOOL success = [[NSFileManager defaultManager] startDownloadingUbiquitousItemAtURL:url error:&error]; 
     if (!success) 
     { 
      // failed to download 
     } 
     else 
     { 
      NSDictionary *attrs = [url resourceValuesForKeys:@[NSURLUbiquitousItemIsDownloadedKey] error:&error]; 
      if (attrs != nil) 
      { 
       if ([[attrs objectForKey:NSURLUbiquitousItemIsDownloadedKey] boolValue]) 
       { 
        // already downloaded 
       } 
       else 
       { 
        NSMetadataQuery *query = [[NSMetadataQuery alloc] init]; 
        [query setPredicate:[NSPredicate predicateWithFormat:@"%K > 0", NSMetadataUbiquitousItemPercentDownloadedKey]]; 
        [query setSearchScopes:@[url]]; // scope the search only on this item 

        [query setValueListAttributes:@[NSMetadataUbiquitousItemPercentDownloadedKey, NSMetadataUbiquitousItemIsDownloadedKey]]; 

        _fileDownloadMonitorQuery = query; 

        [[NSNotificationCenter defaultCenter] addObserver:self 
                  selector:@selector(liveUpdate:) 
                   name:NSMetadataQueryDidUpdateNotification 
                   object:query]; 

        [self.fileDownloadMonitorQuery startQuery]; 
       } 
      } 
     } 
    }); 
} 

- (void)liveUpdate:(NSNotification *)notification 
{ 
    NSMetadataQuery *query = [notification object]; 

    if (query != self.fileDownloadMonitorQuery) 
     return; // it's not our query 

    if ([self.fileDownloadMonitorQuery resultCount] == 0) 
     return; // no items found 

    NSMetadataItem *item = [self.fileDownloadMonitorQuery resultAtIndex:0]; 
    double progress = [[item valueForAttribute:NSMetadataUbiquitousItemPercentDownloadedKey] doubleValue]; 
    NSLog(@"download progress = %f", progress); 

    // report download progress somehow.. 

    if ([[item valueForAttribute:NSMetadataUbiquitousItemIsDownloadedKey] boolValue]) 
    { 
     // finished downloading, stop the query 
     [query stopQuery]; 
     _fileDownloadMonitorQuery = nil; 
    } 
} 
+0

no funciona para mí cuando el alcance se define como url. – Marcin

+0

Esto actualmente no funciona para mí (8.1.2), ya sea configurando el alcance como una URL o 'NSMetadataQueryUbiquitousDocumentsScope', y cambiando del' NSMetadataUbiquitousItemIsDownloadedKey' obsoleto a 'NSMetadataUbiquitousItemDownloadingStatusKey' la devolución de llamada' liveUpdate: 'nunca ocurre. – voidref

+0

También podría observar que 'resourceValuesForKeys' en una URL UbiquityContainer válida siempre devuelve una matriz vacía para mí. – voidref

10

Se trata de un problema conocido. Se ha reproducido here y here.

Parece que agregar una demora ayuda a aliviar una condición de raza desconocida, pero aún no existe una solución conocida. Desde el 21 de marzo:

De todos modos, me preguntaba si alguna vez pasaste tu problema principal en este artículo ? Es decir, con el tiempo, la sincronización parece disminuir y las notificaciones de importación dejan de llegar o están incompletas. Usted había identificado que añadiendo un retraso en responder a la notificación de importación tenía algún valor , pero finalmente resultó ser poco confiable.

Y desde el OP del artículo enlazado:

El problema que había parecido que es causada por una condición de carrera. A veces, I recibía la notificación de que mi tienda persistente había sido actualizada desde iCloud, pero la información actualizada no estaría disponible todavía. Esto pareció suceder aproximadamente 1/4 de las veces sin demora, y aproximadamente 1/12 parte del tiempo con la demora.

No era como la estabilidad degradada ...el sistema siempre detectaría la actualización la próxima vez que inicié la aplicación, y la resolución automática de conflicto solucionó el problema. Y luego continuaría funcionando normalmente. Pero, eventualmente, dejaría caer otra actualización.

...

A cierto nivel, creo que sólo tenemos que confiar en que iCloud finalmente empujar a la información, y nuestro código de resolución de conflictos (o la resolución de conflictos automática de datos básicos) solucionará cualquier problema que surgen.

Aún así, espero que Apple solucione el error de condición de carrera. Eso simplemente no debería ocurrir .

Por lo tanto, en el momento de escribir este documento, al menos, las descargas de iCloud con esta API deberían considerarse no confiables.

(Additional reference)

+0

Gracias por los enlaces - muy desafortunado que Apple se contenta con dejar las cosas en tal estado lamentable. –

+0

@MrGomez ... Aparentemente estoy teniendo la misma situación aquí. ¿Dónde debería poner el retraso? Esa parte no fue muy clara para mí :( – nacho4d

+0

@ nacho4d Recomendaría mirar el [paquete de códigos] (http://www.freelancemadscience.com/files/MultiDocument.zip) (archivo ZIP) vinculado por el artículo antes mencionado sobre el tema. Hablando con franqueza, debe agregar un retraso entre la transferencia de cada archivo individual a mi entender. Si necesita ayuda adicional, le recomiendo publicar una pregunta de seguimiento con su código específico dado. ¡La mejor de las suertes para usted! :) – MrGomez

5

encontrado con esta misma situación yo recientemente - al igual que anteriormente, vi la descarga ocurre con claridad, pero NSURLUbiquitousItemIsDownloading, y todas las otras llaves, siempre volvieron false. Un colega mencionó que un ingeniero de iCloud había recomendado crear un nuevo NSURL para verificar estas claves NSURLUbiquitousItem, ya que es posible que los metadatos no se actualicen (y aparentemente no se actualizarán) una vez que se hayan creado. Creé un nuevo NSURL antes de verificar, y de hecho reflejó el estado actual.

Para no descontar las condiciones de carrera mencionadas por @MrGomez, que son problemas importantes (especialmente frecuentes en iOS 5, y nos causaron muchos dolores de cabeza), pero no creo explicar el problema descrito anteriormente.

EDITAR: Para crear el nuevo NSURL utilicé [NSURL fileURLWithPath:originalURL.path]. Aunque un poco desordenado, fue lo primero que funcionó de manera confiable. Acabo de probar [originalURL copy] y terminé con viejos metadatos nuevamente, así que aparentemente también fue copiado.

Por seguridad, o hasta que se documente más, planeo asumir que a menos que se cree un nuevo NSURL antes de cualquier llamada getResourceValue:forKey:, se devolverán los metadatos obsoletos.

+0

Entonces, cuando se proporciona un objeto 'NSURL' para examinar, ¿cómo lo hace? crear el nuevo 'NSURL' para verificar el estado actualizado? ¿Llamas a 'copiar' en el anterior, o usas algún otro constructor, con valores obtenidos del original? Y supongo que volverás a crear este nuevo 'NSURL' al comienzo de cada uno de tus 'pases' de descarga. –

+1

Me lo había preguntado también. Actualizado con mis hallazgos –

0

que estoy enfrentando el mismo problema y he pasado muchos días tratando de encontrar una solución de este error ...

Por desgracia, ninguna de las soluciones que aquí funciona para mí y veo que el documento que quiero abrir está siempre actualizado después de 2 intentos para abrirlo.

Así que la solución para mí es abrir el documento dos veces de esta manera:

  • haga una abertura ciega en primer lugar;
  • Realice un cierre ciego;
  • Esperando el estado descargado;
  • A continuación, abra el documento.

Soy consciente de que esta es una manera muy sucia para hacer el trabajo, pero funciona bien para mí :-)

Cuestiones relacionadas