Способ загрузки файлов iCloud? Очень смущает?
У меня есть базовая поддержка iCloud в моем приложении (синхронизация изменений, создание вездесущих и т.д.), но одним из основных недостатков до сих пор было отсутствие поддержки загрузки файлов, которые существуют (или имеют изменения) в облаке, но не синхронизируются с тем, что в настоящее время находится на диске.
Я добавил следующие методы в мое приложение на основе некоторого кода, предоставленного Apple, с помощью нескольких настроек:
Методы загрузки:
- (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]];
}
}
}
И код, который запускает/проверяет загрузки:
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];
}
Насколько я могу судить, приведенный выше код кажется прекрасным, но когда я делаю большое количество изменений на устройстве A, сохраните эти изменения, а затем откройте устройство B (чтобы запросить загрузку на устройстве B) это то, что я вижу в консоли:
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
Так по какой-то причине:
- Загрузка файла начинается без ошибок
- Атрибуты файла для состояния загрузки файла всегда возвращают, что он не загружается, не загружается, а прогресс составляет 0 процентов.
- Я застрял в цикле навсегда, даже если размер файла изменяется между проверками.
Что я делаю неправильно?
Ответы
Ответ 1
Через несколько лет я все еще испытываю периодические (хотя и гораздо реже после некоторых обходных путей в других ответах) проблемы с загрузкой файлов. Итак, я связался с разработчиками Apple, предлагая техническую проверку/обсуждение, и вот что я нашел.
Периодически проверка статуса загрузки того же NSURL
, даже если вы его воссоздаете, не является предпочтительным методом проверки состояния. Я не знаю, почему это не так - похоже, он должен работать, но это не так. Вместо этого, как только вы начнете загружать файл, вы должны зарегистрировать наблюдателя с NSNotificationCenter
для выполнения этой загрузки, поддерживая ссылку на запрос для этого файла. Вот пример кода, который мне был предоставлен. Я реализовал его (с некоторыми настройками для приложения) в своем приложении, и, похоже, он выполняет гораздо более подходящий подход.
- (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 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;
}
}
Ответ 2
Это известная проблема. Она была воспроизведена здесь и .
Кажется, добавление задержки помогает смягчить неизвестное состояние гонки, но пока не существует известного способа обхода проблемы. С 21 марта:
В любом случае, мне было интересно, если вы когда-нибудь преодолели основную проблему в этом статья? То есть со временем синхронизация, похоже, ухудшается, и импорт уведомления перестают поступать или являются неполными. Вы определили, что добавление задержки в ответе на уведомление об импорте но в конечном итоге оказался ненадежным.
И из OP связанной статьи:
Проблема, которая, как мне казалось, была вызвана состоянием гонки. Иногда я получит уведомление о том, что мой постоянный магазин был обновлен от iCloud - но обновленная информация пока недоступна. Кажется, это произошло примерно через 1/4 времени без задержки, и около 1/12-й раз с задержкой.
Это не было похоже на ухудшение стабильности... система всегда будет ловить обновление при следующем запуске приложения и автоконфликт разрешение проблемы. И тогда он будет продолжать функционировать как обычно. Но, в конце концов, он потеряет еще одно обновление.
...
На каком-то уровне я думаю, нам просто нужно верить, что iCloud будет в конечном итоге вытолкнуть информацию, и наш код разрешения конфликтов (или автоматическое разрешение конфликтов данных ядра) устранит любые проблемы которые возникают.
Тем не менее, я надеюсь, что Apple исправит ошибку гонки. Это просто не должно произойдет.
Итак, на момент написания, по крайней мере, загрузка iCloud с использованием этого API должна рассматриваться как ненадежная.
(Дополнительная ссылка)
Ответ 3
В последнее время я столкнулся с такой же ситуацией - так же, как и выше, я видел, что загрузка явно происходит, но NSURLUbiquitousItemIsDownloading
и все остальные ключи всегда возвращались false
. Коллега упоминал, что инженер iCloud рекомендовал создать новый NSURL для проверки этих NSURLUbiquitousItem
ключей, поскольку метаданные могут (и, по-видимому, не будут) обновляться после его создания. Я создал новый NSURL перед проверкой, и он действительно отразил текущее состояние.
Не обесценивать условия гонки, упомянутые @MrGomez, которые являются значительными проблемами (особенно распространенными в iOS 5, и вызвали у нас много головных болей), но я не верю, объясняю проблему, описанную выше.
EDIT:
Чтобы создать новый NSURL
, я использовал [NSURL fileURLWithPath:originalURL.path]
. В то время как немного беспорядочно, это было первое, что работало надежно. Я просто попробовал [originalURL copy]
и снова получил старые метаданные, поэтому, видимо, он тоже был скопирован.
В целях безопасности или до его дальнейшего документирования я предполагаю, что если новый NSURL
не будет создан до вызова getResourceValue:forKey:
, будут возвращены устаревшие метаданные.
Ответ 4
Я столкнулся с той же проблемой, и я потратил много дней, пытаясь найти обходной путь для этой ошибки...
К сожалению, ни одно из решений здесь не работает для меня, и я вижу, что документ, который я хочу открыть, всегда обновляется после двух попыток его открытия.
Итак, решение для меня состоит в том, чтобы открыть документ дважды:
- Сначала сделайте слепое открытие;
- Сделать закрытие закрытия;
- Ожидание загруженного состояния;
- Затем откройте документ.
Я знаю, что это очень грязный способ выполнить эту работу, но он отлично работает для меня: -)