Search code examples
iosobjective-ccachingparse-platformpfquery

PFQuery cache is null all the time


I'm setting my PFQuery object's cache policy to kPFCachePolicyCacheThenNetwork but the cache remains empty all the time. Can some one help me out what's going on?

The below code returns the cached results empty all the time

-(void)doSomeQuery
{
   PFQuery *query = [PFQuery queryWithClassName:kMySpecialClass];
   [query whereKey:kDateExpires greaterThan:[NSDate date]];
   query.cachePolicy = kPFCachePolicyCacheThenNetwork;
   NSLog(@"CACHED ? =  %i",[query hasCachedResult]);//Nope, no matter what returns NO
   [query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {


    NSLog(@"RETURNED: %@", objects);//1st cache (null) then network gets the data

  }];

}

Solution

  • In order to have a cached result, the query needs to match (or be equal to) an earlier query where the result was cached. The query posted in the question is different every time, because it is qualified by [NSDate date].

    To be sure a query has a cached result, keep the same PFQuery object around. If you set kPFCachePolicyCacheThenNetwork, and run find... on it a second time, you'll get a cached result.

    EDIT - Working around this depends on how sensitive the system is to time. Let's take the query to mean: give me unexpired instances of MySpecialClass, where unexpired means expiration in the future.

    Let's take the usage of local cache to mean, I want a fast query and/or offline operation sometimes, and I'm willing to trade away truth relative to what's on the server (maybe the server doesn't get new instances of MySpecialClass very often anyway).

    To the extent these things are true, you can work around the OP problem by having two types of query:

    1) A refresh query, done less often, that blows away the cache and gets the newest unexpired stuff from the server. This query is implemented exactly as in the OP code, but using the default cache strategy: network only.

    2) A maintenance query, done more frequently, that relies on the cache, but runs fast and offline. This query still wants to omit expired instances of my special class, but we do that in code after the query. Implement this query to be a property that persists (at least during execution, maybe between executions, but that's a different topic) and use cache-then-network as follows:

    @property (strong) PFQuery *maintenanceQuery;
    
    // lazily init
    - (PFQuery *)maintenanceQuery {
        if (!_maintenanceQuery) {
           // op code, including cachePolicy = kPFCachePolicyCacheThenNetwork;
        }
        return _maintenanceQuery;
    }
    
    // based on some timing decision, either run the refresh query or...
    - (void)runMaintenanceQuery:(void (^)(NSArray *, NSError *))completion {
        [self.maintenanceQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
            NSDate *now = [NSDate date];
            NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(PFObject *mySpecialInstance, NSDictionary *bind){
                NSDate *expiration = [mySpecialInstance valueForKey:kDateExpires];
                return now == [expiration earlierDate:now];
            }];
    
            NSArray *unexpired = [objects filteredArrayUsingPredicate:predicate];
            completion(unexpired, error);
        }];
    }
    

    What's nice about the cache-then-network feature is that in connected situations, your maintenance query still remains relatively up-to-date with the server, since after the query, silently, and if possible, the real query gets made, and new instances appear. Some will be expired of course, because time has marched forward, but we take care of that with the in-memory filter afterwards.