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