5

I'm making a backup managrer for my App (via iCloud). I did some tests and the basics worked. But few days later it stopped. I'm using NSMetadataQuery for searching if backup file exists. My backup files are named e.g. Backup29112011154133.xml where numbers represent date of the backup (formatted as ddMMyyyyHHmmss). I check for it in -viewDidAppear:

- (void)viewDidAppear:(BOOL)animated {
    [self checkForRemoteFile];
}

- (void)checkForRemoteFile {
    NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
    if (ubiq) {
        NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
        [query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
        NSPredicate *pred = [NSPredicate predicateWithFormat:@"%K like 'Backup*'",NSMetadataItemFSNameKey];
        [query setPredicate:pred];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
        [query startQuery];
    } else {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"iCloud is unavailable at the moment" message:nil delegate:self cancelButtonTitle:@"Close" otherButtonTitles:nil];
        [alert setTag:TAG_ALERT_NOICLOUD];
        [alert show];
    }
}

- (void)queryDidFinishGathering:(NSNotification *)notif {
    NSMetadataQuery *query = [notif object];
    [query disableUpdates];
    [query stopQuery];
    [self loadRemoteFile:query];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query];
}

- (void)loadRemoteFile:(NSMetadataQuery *)query {
    if ([query resultCount] == 1) {
        canRestore = YES;
        NSMetadataItem *item = [query resultAtIndex:0];
        // parse the backup file
        [self.tableView reloadData];
    } else {
        canRestore = NO;
        modifDate = @"never";
        backupInfoLoaded = YES;
        [self.tableView reloadData];
    }
}

The problem is that - (void)queryDidFinishGathering:(NSNotification *)notif is never executed. I put breakpints and NSLogs ion there but nothing happend.

I also tried to check for other notifications e.g. 'query did start gathering' and 'query process'. Only 'query did start' notification is posted.

I also have AppID with iCloud registered and entitlements file attached.

Can you help me out what's going on? Maybe I missed something?

akashivskyy
  • 44,342
  • 16
  • 106
  • 116

4 Answers4

17

First of all NSMetadataQuery doesn't works if startQuery was called not from the MaintThread. There is possibility that predicate fails for every path also. Following code works for me.

NSURL *mobileDocumentsDirectoryURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
...
query.predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:@"%%K like \"%@*\"", [mobileDocumentsDirectoryURL path]], NSMetadataItemPathKey];
[query startQuery];
FalconSer
  • 312
  • 1
  • 5
  • Yeah, but it worked and suddenly stopped, so I don't think that this is the problem.. – akashivskyy Dec 01 '11 at 19:44
  • FalconSer, I've tried a query with above predicate and I get an exception. It says cannot parse regex, etc. It should be `@"%K BEGINSWITH %@", NSMetadataItemPathKey, [mobileDocumentsDirectoryURL path]]]` (note there is only one percent , there is no quotes around `%@` and the arguments are in inverted order. If needed use `BEGINGWITH[cd]` instead of `BEGINGWITH`) – nacho4d Jun 13 '12 at 08:24
  • 1
    Thank you! I was not calling from the main thread and your answer really helped me. – SeanR Sep 02 '15 at 12:50
9

FIXED by creating ivar for NSMetadataQuery.

I don't know why the application can't read data without NSMetadataquery ivar.

Community
  • 1
  • 1
akashivskyy
  • 44,342
  • 16
  • 106
  • 116
  • 4
    if you are using ARC, your NSMetaDataQuery gets deallocated after the scope ends. You need a iVar to retain it – Mugunth Feb 11 '12 at 04:48
2

Unfortunately there have been many problems with iCloud and using NSMetaDataQuery. To be honest with you the best source as of now for all your iCloud related questions is the Apple Developer Forums. Today Apple released iOS 5.1 beta, and the release notes STILL say that NSMetaDataQuery isn't functioning properly. It's extremely frustrating that iCloud still isn't working properly, but sadly there's nothing we can do.

pob21
  • 1,918
  • 2
  • 16
  • 17
  • Is there any other way to work with files on iCloud without using NSMetadataQuery? – akashivskyy Nov 29 '11 at 18:55
  • Since the iCloud Documents API has been thru many "reincarnations" since it was released, my knowledge may be a little dated(I've been working exclusively with Core Data, UIManagedDocument), but when I used to load data using NSMetaDataQuery I had to load every file. And no as frustrating as it is the only way to work with iCloud is the use Apples documentation code(NSMetaDataQuery). – pob21 Dec 01 '11 at 22:26
1

This problem still persists. I have been able to trace it to the following divergence:

  • If you limit your search predicate on the query to the name key,

for example

[NSPredicate predicateWithFormat:@"%K like[cd] %@", NSMetadataItemFSNameKey, @"*"]

then it will work as expected (posting all four query lifecycle notifications).

  • If, however, you try either a compound predicate or try to work with the path,

as in

[NSPredicate predicateWithFormat:@"%K BEGINSWITH %@", NSMetadataItemPathKey, [self cloudDocumentsURL].path]

OR

[NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:namePred, pathPred, nil]];

Then only the initial notification will be posted.

I have tried literally hundreds of combinations of these configurable variables in multiple test and intended-for-production apps over the last year and have yet to find a counterexample to this hypothesis.

Unfortunately, NSMetadataQuery just doesn't work for ubiquitous stores (as of 10.8).

My workaround is to get the raw results from the query and work mostly on a bound NSArrayController which can have its results filtered. This will mean refactoring away from query.results for most existing code and there is a performance hit (presumably) but it is the only way I have found. I would love an alternative.

SG1
  • 2,871
  • 1
  • 29
  • 41