Parse.com Query: Cache is always empty (iOS) - xcode

I'm writing an iOS-App using Parse.com as a backend.
In the - (PFQuery)queryForTable method of my PFQueryTableViewController I'm retrieving a set of data from Parse, but I'm unable to cache this query in order to support functionality when the device is currently offline.
The method looks as followed:
- (PFQuery *)queryForTable {
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
[query whereKey:#"city" equalTo:[[NSUserDefaults standardUserDefaults] objectForKey:#"city"]];
// userMode is active when a user is logged in and is about to edit coins
if (self.userModeActive) {
[query whereKey:self.textKey equalTo:self.user[#"location"]];
}
// dateFilter is active when view is pushed from an event
if (self.dateFilterActive) {
[self createDateRangeForFilter];
[query whereKey:#"date" greaterThan:[[self createDateRangeForFilter] objectAtIndex:0]];
[query whereKey:#"date" lessThan:[[self createDateRangeForFilter] objectAtIndex:1]];
} else {
// Add a negative time interval to take care of coins when it's after midnight
[query whereKey:#"date" greaterThanOrEqualTo:[[NSDate date] dateByAddingTimeInterval:-(60 * 60 * 6)]];
[query orderByAscending:self.dateKey];
}
// locationFilter is active when view is pushed from a location profile
if (self.locationFilterActive) {
[query whereKey:#"location" equalTo:self.locationToFilter];
}
// If no objects are loaded in memory, look to the cache first to fill the table
// and then subsequently do a query against the network.
if (self.objects.count == 0) {
query.cachePolicy = kPFCachePolicyCacheThenNetwork;
}
if ([query hasCachedResult]) {
NSLog(#"hasCache");
} else {
NSLog(#"chache empty");
}
return query;
}
[query hasCachedResults] always returns false in this case.
In another class, I'm doing the almost exactly same query (on a different Parse-Class) and it caches automatically. The only difference is, that this other query contains PFFiles.
This may be a dumb question but I'm stuck with it for days now.
Thanks for any help and let me know if I can give you more information.

The code guards the setting of cache policy with a conditional if (self.objects.count == 0). It would seem that you're using the cache when there are zero objects and not using it after the query has succeeded. Since the default is to not use the cache, the code is arranged to never use it.
Just remove the conditional, or use the cache conditionally upon [query hasCachedResult]
EDIT - It is still the case that the cache policy can/should be set unconditionally, but a query can have hasCachedResults only if its criteria don't change after the find (I don't see a place in the docs confirming this, but it stands to reason). To insure that a query can return cached results, leave its criteria unchanged after the find.

The [NSDate date] avoid the cache of PFQuery. Here is a work-around:
do not query NSDate at viewDidLoad
but do it in viewDidAppear
The code:
- (PFQuery *)queryForTable {
PFQuery *query = [PFQuery queryWithClassName:self.parseClassName];
// 1. load from cache only when viewDidLoad
// setup query WITHOUT NSDate "where" condition
if (self.shouldQueryToNetwork) {
// 2. update objects with date condition only when view appeared
[query whereKey:#"date" greaterThan:[NSDate date]];
}
return query;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.shouldQueryToNetwork = YES;
// Sync objects with network
[self loadObjects];
}

Related

CouchbaseLite 1.3.0 view querying id of doctype

I am using couchbase lite 1.3.0 for an ios app. I am saving the model objects as docs in the db. However I am changing the 'doctype' dynamically while saving. Upon querying with 'docType' id , the cbl view emits wrong/unmatching 'docType'. Couldn't figure out how to fix this.
[view setMapBlock: MAPBLOCK({
if ([doc[#"docType"] isEqual: #"docType1"])
{
emit(doc, nil);
}
})
version: #"1"];
CBLQuery* query = [view createQuery];
query.descending = NO;
CBLQueryEnumerator* result = [query run: &error];
for (CBLQueryRow* row in result) {
//The rows emitted doc of 'doctype2'
}
Changing docType is OK I think, the problem is you map function is hard coded against a query parameter which is not encouraged and also you emit a full doc as well.
I can achieve what you are trying to do with this:
[view setMapBlock: MAPBLOCK({
emit(doc[#"docType"], nil);
})
version: #"1"];
and then query using the docType1 as a parameter, if you need the doc just set the preFetch totrue`
I'm aware that this will introduce 'redundancy' to what you want, but I think it solves the problem

Storing Dictionary in NSUserDefaults causing EXC_BAD_ACCESS

//Declarations
var scoreIncrement:Int = 0
var team = ""
var userScore = Dictionary<String,Int>()
//Set Values
team = "USA"
userScore[team] = 0
//Store in NSUserDefaults
NSUserDefaults.standardUserDefaults().setObject(userScore, forKey: "userScore")
NSUserDefaults.standardUserDefaults().synchronize()
//Increment Score
scoreIncrement = 1
userScore[team]! += scoreIncrement
Above code gives 'EXC_BAD_ACCESS(code=EXC_I386_GPFLT)' error on last line of the code. However if I comment out line:
NSUserDefaults.standardUserDefaults().setObject(userScore, forKey: "userScore")
The error goes away. Any idea why this is happening, I am not even retrieving the dictionary from NSUserDefaults yet.
Solved the problem by retrieving the dictionary back out of NSUserDefaults right after saving it with:
var userScoreTemp : AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey("userScore")
if userScoreTemp != nil {
userScore = userScoreTemp! as Dictionary
}
I had this problem as well. It turned out to be a KVO issue. It's possible that you have deallocated an object that was observing this value and so the message was being sent and not being received as it expects to be.
My solution was to remove the observer from the associated deallocated object. Always remember if you addObserver:forKeyPath:options:context: to match it with an associating removeObserver:forKeyPath: call.
I had the same problem.
I stored String value in NSUserDefaults like:
[[NSUserDefaults standardUserDefaults] stValue:#"Rahul" forKey:#"Name"];
After a certain period it crashed at the same point, writing the same value. The cause was that after this period I registered for changes in the NSUserdefaults with code like:
[[NSNotificationCenter defaultCenter] addObserver:self selector: #selector(valueChange) name:NSUserDefaultsDidChangeNotification object:nil];
This was done in an importer at initialization. After the importer was deallocaed, a new write to the user defaults was triggering the observer for an object that was gone. Crash.
The solution was of course to remove the observer .
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObserver:self forKeyPath:#"Name"];

NSTextField with NSFormatter results in broken continuous binding

I have a textfield which has to be unique so I added my custom NSFormatter (see below)
The formatter works, as you can see on the screenshot, but the continuous binding, which I am using is broken, so for example the bound text does no longer get updated in real-time.
I found a possible cause here, but I don't know how to work around this problem and re-enable the continuous binding:
...
12. If the view has an NSFormatter attached to it, the value is
formatted by the NSFormatter instance. Proceed to Step 17.
...
17. The updated value is displayed in the user interface.
So it looks like it's intentionally skipping the steps we want. This
is very annoying. I tried NSValueTransformer, but adding that to an
editable NSTextField makes it non-editable.
My formatter
- (BOOL)getObjectValue:(out id *)obj forString:(NSString *)string errorDescription:(out NSString **)error {
if([string isNotEqualTo:#"todo-invalid-value"]){
*obj = string;
NSLog(#"YES");
return YES;
} else {
if(error){
*error = #"ERROR: not allowed";
}
return NO;
}
}
- (NSString *)stringForObjectValue:(id)obj {
return (NSString *)obj;
}
Working validation
Please note that the title of the list item should be updated with the text, that I entered in the textfield.
I ran into the same problem over the weekend, and eventually discovered a post from 2008 by Yann Disser on the cocoa-dev mailing list which shed some light on my problem.
I had an existing NSFormatter that was working fine and when I broke down the components, so I spent a little more time on it this morning and located Yann's post.
The key is that you need to return a different object than the one that is passed in. It's subtle, but the docs say: If conversion is successful, upon return contains the object created from string.
The problem I was having stemmed from the fact that the NSString that was coming in was actually an NSMutableString and was getting modified later.
Here's the code modified to return [NSString stringWithString: string], which should fix your problem:
- (BOOL)getObjectValue:(out id *)obj forString:(NSString *)string errorDescription:(out NSString **)error {
if([string isNotEqualTo:#"todo-invalid-value"]){
*obj = [NSString stringWithString: string];
NSLog(#"YES");
return YES;
} else {
if(error){
*error = #"ERROR: not allowed";
}
return NO;
}
}

NSFetchResultController: Only Initial Load shows no data in the tableViewController's table

I have:
#interface CMainTableViewController : UITableViewController
In that class I have an NSFetchResultController.
I have the following separate data manager class:
DB_ManagerSingleton
//Here I have a UIManagedDocument that creates the DB/persistent store
To open my persistent store I do the following from my DB_ManagerSingleton class:
UIManagedDocument *fileDB = [[UIManagedDocument alloc]initWithFileURL:url];
if([[NSFileManager defaultManager] fileExistsAtPath:self.fileDB.fileURL.path] != TRUE)
{
[self.fileDB saveToURL:self.fileDB.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success)
{
[self completeDBSetup:success];
}];
}//End if
else if(self.fileDB.documentState == UIDocumentStateClosed)
{
[self.fileDB openWithCompletionHandler:^(BOOL success)
{
[self completeDBSetup:success];
}];
}//End else if
else if(self.fileDB.documentState == UIDocumentStateNormal)
{
[self completeDBSetup:TRUE];
}//End else if
The completeDBSetup places 4 NSManageObjects into the NSManagedObjectContext and notifies the registered observers of the success that the persistent store was opened:
if([CLevelFactory createLevels] == TRUE)
[self.fileDB.managedObjectContext save:&error];
for(id<DB_StateNotification> observer in self.arrNotificationDelegates)
{
if(observer != nil)
[observer DB_StateChange:self.isDBSetup];
}//End for loop
Now, in this case the observer is my TableViewController class. Within this class I have implemented all the methods in the NSFetchedResultsControllerDelegate and the implementation is copied exactly from the Coredata books example.
http://developer.apple.com/library/ios/#samplecode/CoreDataBooks/Listings/Classes_RootViewController_m.html#//apple_ref/doc/uid/DTS40008405-Classes_RootViewController_m-DontLinkElementID_14
The way I set up my tableView controller in the DB_StateChange method from above is as follows:
self.fetchResultController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest
managedObjectContext:dbMgr.fileDB.managedObjectContext
sectionNameKeyPath:#"levelID"
cacheName:nil];
self.fetchResultController.delegate = self;
NSError *error = nil;
self.bQuerierYourData = [self.fetchResultController performFetch:&error];
NSArray *fetchedObjects = [self.fetchResultController fetchedObjects];
//NOTE: I ALWAYS have a count greater than 0. Even on the first run.
if(fetchedObjects.count > 0)
[self.tableView reloadData];
Now the issue is this:
The very first time my application launches I get squat in my table view. I get a fetchedObjects.count of 4 - which is what I expect, but not'a in the table view. Now, without touching anything. Not inserting, updating, or deleting anything in the persistent store, I just quick the application. In this case hitting the stop button on the debugger. When I go ahead and restart the app, everything comes in beautifully. I don't insert data every time the app starts, I test and if the data already exists, I just move on without inserting any data.
Does anyone have any idea what is going on here?

Get variable from void function in Objective C

I'm VERY new to Objective C and iOS development (like 5 hours new :-). I've got some code that calls an API to authenticate a user and returns a simple OK or FAIL. I can get the result to write to the console but what I need to do is get that result as part of my IBAction.
Here's the IBAction code:
- (IBAction) authenticateUser
{
[txtEmail resignFirstResponder];
[txtPassword resignFirstResponder];
[self performAuthentication];
if (authResult == #"OK")
What I need is for authResult to be the JSON result (OK or FAIL). Here is the code that gets the result:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[connection release];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(#"%#", responseString);
[responseData release];
NSMutableDictionary *jsonResult = [responseString JSONValue];
if (jsonResult != nil)
{
NSString *jsonResponse = [jsonResult objectForKey:#"Result"];
NSLog(#"%#", jsonResponse);
}
}
Thank you so much for any help and sorry if I'm missing something obvious!
I'm a little confused as to what's going on here... it looks like your -performAuthentication method must start an asynchronous network request via NSURLConnection, and your connection's delegate's -connectionDidFinishLoading: gets to determine the result of the request. So good so far? But your -authenticateUser method expects authResult to be determined as soon as -performAuthentication returns. If the network request is asynchronous, that's not going to happen. If I'm following you, I think you need to do the following:
Fix up -connectionDidFinishLoading: so that it actually sets authResult based on the Result value in jsonResponse. I'm sure you'd get around to this at some point anyway.
Change -authenticateUser such that it doesn't expect to have an answer immediately. You've got to give the network request a chance to do its thing.
Add another method, possibly called -authenticationDidFinish or something along those lines. Everything currently in -authenticateUser from the 'if (authResult...' to the end goes in this new method.
Call the new method from -connectionDidFinishLoading:.
Fix your string comparison. If you want to compare two strings in Cocoa, you say (for example):
if ([authResult isEqualToString:#"OK") { }

Resources