I have a JSON request which returns different parameters, name for example. I would like to cache a variable for the name parameter, which I can view in the app later.
For example: name from JSON request = david. the variable is called firstname. firstname should equal "david". firstname should be stored so I can view it in all parts of my apps.
For something simple as a string, the quick and dirty solution is to store it in NSUserDefaults.
Storing
[[NSUserDefaults standardDefaults] setObject:firstname forKey:#"kUserFirstName"];
[[NSUserDefaults standardDefaults] synchronize];
Retrieving
NSString *string = [[NSUserDefaults standardDefaults] objectforKey:#"kUserFirstName"];
If it gets more complicated than that, you have to consider a more structured persistence store. A valid option is CoreData.
There exist a few frameworks that might help you in storing JSON resources in CoreData, the most interesting being RestKit.
First off, you might consider checking out RestKit as it successfully accomplishes a whole slew of server interaction and CoreData persistence in iOS.
I'm a little short on time (on a lunch break here) so I'll just lazily post an example from an app I have.
- (void)loadFiltersFromJSON {
NSString *path = [[NSBundle mainBundle] pathForResource:#"FreeFilterBank" ofType:#"json"];
NSData *filterData = [NSData dataWithContentsOfFile:path];
NSError *err;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:filterData options:kNilOptions error:&err];
if (err) { //TODO this can be removed prior to shipping the app
NSLog(#"%#", err);
}
NSArray *definedFilters = [json objectForKey:#"Filter"];
NSManagedObjectContext* moc = [self managedObjectContext];
for (NSDictionary *filter in definedFilters) {
NSString *name = [filter valueForKey:#"name"];
BOOL exists = [self alreadyExists:name inManagedObjectContext:moc];
if (!exists) {
NSString *imageNamed = [filter valueForKey:#"imageName"];
NSString *filterDesignator = [filter valueForKey:#"filterDesignator"];
NSString *paid = [filter valueForKey:#"paidOrFree"];
[self createFilterWithName:name imageNamed:imageNamed filterDesignator:filterDesignator paidOrFree:paid];
}
}
}
- (BOOL)alreadyExists:(NSString*)filterNamed inManagedObjectContext:(NSManagedObjectContext*)moc {
NSPredicate* predicate = [NSPredicate predicateWithFormat:#"name == %#", filterNamed];
NSEntityDescription* description = [NSEntityDescription entityForName:#"Filter" inManagedObjectContext:moc];
NSFetchRequest* request = [[NSFetchRequest alloc] init];
[request setEntity:description];
[request setPredicate:predicate];
NSError* error;
NSArray* fetchedResult = [moc executeFetchRequest:request error:&error];
if (error) {
NSLog(#"%#",error.localizedDescription);
}
if (fetchedResult.count == 0) {
return NO;
}
else {
return YES;
}
}
- (void)createFilterWithName:(NSString*)name imageNamed:(NSString*)imageName filterDesignator:(NSString*)designator paidOrFree:(NSString *)paid {
NSManagedObjectContext* moc = [self managedObjectContext];
Filter* newFilter = [NSEntityDescription insertNewObjectForEntityForName:#"Filter" inManagedObjectContext:moc];
newFilter.name = name;
newFilter.imageName = imageName;
newFilter.filterDesignator = designator;
newFilter.paidOrFree = paid;
NSError* error;
[moc save:&error];
if (error) {
NSLog(#"%#",error.localizedDescription);
}
}
TL;DR This loads data from a JSON stored in the bundle, checks the SQLite data store to see if we already have something with the same name, and creates a new persistent instance of this object if we don't.
Take this example for what you will, there are many many more invocations for serialized data pulled from the web and persistent data within iOS beyond this one example.
The easiest way is to use NSUserDefaults and set the key to #"firstname" and the value would be #"david". That being said, you might consider using a better persistence model like CoreData. You can also use an Sqlite database or have the key/value saved in a plist. There are a number of ways to do this.
For reference ,see this:
Save string to the NSUserDefaults?
Related
I'll try to expose my problem, because is a bit complex.
I use Core Data and I have a problem with the data stored.
When I use this code:
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"ItemMessage"];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
NSMutableArray *values = [[NSMutableArray alloc] init];
if (error == nil) {
for (int i = 0; i<results.count; i++) {
NSLog(#"results %#",[results objectAtIndex:i]);
ItemMessage *itemMessage = [results objectAtIndex:i];
[values addObject:itemMessage];
}
ecc. the problem is that the value printed by NSLog is correct (the "results" contains something) but the itemMessage contains always 0 key/value pairs (it seems empty).
To understand what is the problem I went back and saw that in insertNewObjectForEntityForName I have also this problem, this is the code that I used when I save the messages data in Core Data:
for (id key in objectMessage) {
ItemMessage *itemmessage = [[ItemMessage alloc] init];
itemmessage.itemMessageId = [key objectForKey:#"itemMessageId"];
itemmessage.message = [key objectForKey:#"message"];
itemmessage.sender = [key objectForKey:#"sender"];
itemmessage.users = [key objectForKey:#"users"];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *newMessage;
newMessage = [NSEntityDescription insertNewObjectForEntityForName:#"ItemMessage" inManagedObjectContext:context];
[newMessage setValue: itemmessage.itemMessageId forKey:#"itemMessageId"];
[newMessage setValue: itemmessage.message forKey:#"message"];
[newMessage setValue: itemmessage.sender forKey:#"sender"];
[newMessage setValue: itemmessage.users forKey:#"users"];
[context save:&error];
if (error != nil) {
NSLog(#"Coredata error");
}
The problem is that newMessage after the insertNewObjectForEntityForName and the setValue contains also 0 key/value pairs.
Can you help me?
You don't seem to insert the new managed objects correctly into the context.
It should be:
for (id key in objectMessage) {
NSManagedObjectContext *context = [appDelegate managedObjectContext];
ItemMessage *itemmessage = (ItemMessage*)[NSEntityDescription insertNewObjectForEntityForName:#"ItemMessage"
inManagedObjectContext:context];
itemmessage.itemMessageId = [key objectForKey:#"itemMessageId"];
itemmessage.message = [key objectForKey:#"message"];
itemmessage.sender = [key objectForKey:#"sender"];
itemmessage.users = [key objectForKey:#"users"];
}
//save your inserts
To create a class file for your managed objects you could:
Go to your model file (xcdatamodeld) ->
select an entity ->
from the menu select:
Editor-> Create NSManagedObjectSubclass -> select the entities your like class files for.
Now you will have managed objects you could access with ease (NSManagedObject subclass) and benefit from CoreData features.
When you insert to manage object contest you have to call save: method, also the saving method should looks something like that:
newMessage = [NSEntityDescription insertNewObjectForEntityForName:#"ItemMessage" inManagedObjectContext:context];
// 2
newMessage.property1 = self.firstNameTextfield.text;
newMessage.property2 = self.lastNameTextfield.text;
if (![context save:&error]) {
NSLog(#"Error: %#", [error localizedDescription]);
}
I would like to know how to fetch the items from my coredata relation ship. I guess it should be a dictionary or arrays or something that gets returned so that you can have your one to many thing.
I am quite lost at this I know how to write/read single objects but this relationship stuff is abit confusing.
I think I have sucsessfully written a relationship to coredata however now I would like to be able to read it to see if I have it right.. I have started writing the method for this but have no idea what to actually do to get all of the information out.
this is the code i have so far.. for both read and write
- (void)writeFin:(NSArray *)recivedProjectData ItemsData:(NSArray *)itemsData {
// WRITE TO CORE DATA
NSManagedObjectContext *context = [self managedObjectContext];
for (NSDictionary *dict in recivedProjectData) {
project = [NSEntityDescription insertNewObjectForEntityForName:#"Project" inManagedObjectContext:context];
project.proNumber = [dict valueForKey:#"ProNumber"];
project.projectDescription = [dict valueForKey:#"Description"];
// project.items = [dict valueForKey:#""]; // this is the relationship for project
}
for (NSDictionary *dict in itemsData) {
items = [NSEntityDescription insertNewObjectForEntityForName:#"Items" inManagedObjectContext:context];
items.Number = [dict valueForKey:#"Number"];
items.Description = [dict valueForKey:#"Description"];
items.comment = [dict valueForKey:#"Comment"];
items.project = project; // this is the relationship for items
[project addItemsObject:items];
}
NSError *error = nil;
if (![__managedObjectContext save:&error]) {
NSLog(#"There was an error! %#", error);
}
else {
NSLog(#"created");
}
}
- (NSMutableArray *)readFin {
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Project" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSMutableArray *projectDictionaryArray = [[NSMutableArray alloc] init];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (ProjectList *projectList in fetchedObjects) {
NSMutableDictionary *tempProjectDictionaryArray = [[ NSMutableDictionary alloc] init];
[tempProjectDictionaryArray setObject:project.proNumber forKey:#"ProNumber"];
[tempProjectDictionaryArray setObject:project.description forKey:#"Description"];
[tempProjectDictionaryArray setObject:project.projectID forKey:#"ProjectID"];
[projectDictionaryArray addObject:tempProjectDictionaryArray];
}
return projectDictionaryArray;
}
So just o reiterate, I would like to know A, is my write method look okay? B, how do you fetch/read the relationship objects from core data.
any help would be greatly appreciated.
A relationship in Core Data isn't an object, its a property which happens to correspond to another object in your model rather than just being a dead end. You're already most of the way there as far as checking whether your relationships are ok as far as I can see -- what you need to do is add one more line in your projectList
[tempProjectDictionaryArray setObject: project.items forKey:#"items"];
the object that you will have added will be an NSSet. You can then check that things are as they should be with a loop like this after you've finished setting things up
NSSet itemsForProject = projectDictionaryArray[someIndex][#"items"]
for (Item* currItem in [itemsForProject allObjects]) {
//access some property of the current item to make sure you have the right ones -- \
description for example
NSLog(#"%#", item.description);
}
This is a SyncEngine from an RW tutorial. I need help understanding how only UPDATED records from the web are fetched and processed into Core Data.
- (void)processJSONDataRecordsIntoCoreData {
NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext];
// Iterate over all registered classes --- CHECK!
for (NSString *className in self.registeredClassesToSync) {
if (![self initialSyncComplete]) {
NSDictionary *JSONDictionary = [self JSONDictionaryForClassWithName:className];
NSArray *records = [JSONDictionary objectForKey:#"results"];
for (NSDictionary *record in records) {
[self newManagedObjectWithClassName:className forRecord:record];
}
} else {
NSArray *downloadedRecords = [self JSONDataRecordsForClass:className sortedByKey:#"objectId"];
if ([downloadedRecords lastObject]) {
NSArray *storedRecords = [self managedObjectsForClass:className sortedByKey:#"objectId" usingArrayOfIds:[downloadedRecords valueForKey:#"objectId"] inArrayOfIds:YES];
int currentIndex = 0;
//if dl count is < current index, there is an updated object dl from the web
for (NSDictionary *record in downloadedRecords) {
NSManagedObject *storedManagedObject = nil;
//Quick check to see if they indeed match, if they do, update the stored object with remote service objects
if ([storedRecords count] > currentIndex) {
storedManagedObject = [storedRecords objectAtIndex:currentIndex];
}
//Othwerwise its a new object and you need to create a new NSManagedObject to represent it in CDdb
if ([[storedManagedObject valueForKey:#"objectId"] isEqualToString:[record valueForKey:#"objectId"]]) {
[self updateManagedObject:[storedRecords objectAtIndex:currentIndex] withRecord:record];
} else {
[self newManagedObjectWithClassName:className forRecord:record];
}
currentIndex++;
}
}
}
// After all NSMO are created in your context, save it!
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"Unable to save context for class %#", className);
}
}];
// Cleanup time
[self deleteJSONDataRecordsForClassWithName:className];
[self executeSyncCompletedOperations];
}
[self downloadDataForRegisteredObjects:NO];
}
From what I understand, on the first or initial sync, it fetches JSONDictionaryForClassWithName which reads the downloaded data from disk and creates a newManagedObjectWithClassName.
My confusion is in the update else block. downloadedRecords is populated from JSONDataRecordsForClass which simply calls JSONDictionaryForClassWithName. Then it checks to see if there is at least 1 object in that array. If there is it does this:
NSArray *storedRecords = [self managedObjectsForClass:className sortedByKey:#"objectId" usingArrayOfIds:[downloadedRecords valueForKey:#"objectId"] inArrayOfIds:YES];
This fetches all managedObjectsForClass:sortedByKey which is below:
- (NSArray *)managedObjectsForClass:(NSString *)className sortedByKey:(NSString *)key usingArrayOfIds:(NSArray *)idArray inArrayOfIds:(BOOL)inIds {
__block NSArray *results = nil;
NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:className];
NSPredicate *predicate;
if (inIds) {
predicate = [NSPredicate predicateWithFormat:#"objectId IN %#", idArray];
} else {
predicate = [NSPredicate predicateWithFormat:#"NOT (objectId IN %#)", idArray];
}
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:
[NSSortDescriptor sortDescriptorWithKey:#"objectId" ascending:YES]]];
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
}];
return results;
}
The next bit which compares the [storedRecords count] > currentIndex is confusing. Can someone please explain this? I think my confusion lies in what the managedObjectsForClass method does with the usingArraysOfIds & inArrayOfIds.
I would expect that at some point it gets the the updatedAt field from the downloaded records and compares it to the updatedAt field of the CoreData fetched records.
This function is processing the stored JSON. The actual remote fetching and updateAt checking happens in downloadDataForRegisteredObjects and mostRecentUpdatedAtDateForEntityWithName.
[storedRecords count] > currentIndex is a bit crazy. Although in defense of the original programmer, writing any decent syncengine will quickly make you go googoo. Basically he needs to work out which records are existing and which ones are new and update the local data store accordingly, that's all.
I had another look and this code is actually horribly broken. It will only works if either you have the same records both locally and remotely. Or if the new objects have an objectId that sort-wise comes after the last object the local store has. Which is not the case with Parse objectId's.
If you are testing with just one device this works because new objects will be inserted locally before being pushed to the server. Therefor you will always have the same amount of records. If additional records get inserted any other way, this code will do weird things.
I have a controller that is the root of a workflow. If there is no data object for the workflow, then I create a new one, and if there is, I use the existing one. I have a property for the model object (an NSManagedObject)
#property (nonatomic, retain) Round *currentRound;
and I call the following whenever the corresponding view is shown
self.currentRound = [self.service findActiveRound];
if (!self.currentRound) {
NSLog((#"configure for new round"));
self.currentRound = [self.service createNewRound];
...
} else {
NSLog(#"configure for continue");
// bad data here
}
The problem is at the place marked in the above, sometimes the data object is corrupted. In the parts I didn't show I set the values on some text fields to represent the values in the model. Sometimes its ok, but eventually the properties on the model object are empty and things break
In the debugger, the reference to the round doesn't appear to change, but NSLogging the relevant properties shows them nullified. debugging seems to delay the onset of the corruption.
I am aware I am not saving the context...should that matter? And if so, how come it doesn't always fail the first time I come back to this controller?
My findActiveRound message is nothing special, but in case it matters
-(Round *) findActiveRound
{
NSLog(#"Create Active Round");
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Round" inManagedObjectContext:context];
[request setEntity:entity];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"isComplete == %#", [NSNumber numberWithBool:NO]];
[request setPredicate:pred];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
if ([results count] == 0) {
return nil;
} else {
return [results objectAtIndex:0];
}
}
Many thanx.
EDIT FOR RESPONSE
By corrupted I mean when I try to get some simple string properties off the model object, I get nil values. So In the code above (where I think I have a round) I do stuff like
self.roundName.text = self.currentRound.venue;
self.teeSelection.text = self.currentRound.tees;
and don't see the data I entered. Since it only fails sometimes, but always fails eventually, I will see the data I entered for a while before its gone.
I'm pretty sure the context is the same. My service is a singleton and created like so
#implementation HscService
+(id) getInstance
{
static HscService *singleton = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
singleton = [[self alloc] init];
});
return singleton;
}
-(id) init
{
if (self = [super init]) {
model = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSString *path = [self itemArchivePath];
NSURL *storeUrl = [NSURL fileURLWithPath:path];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES],
NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES],
NSInferMappingModelAutomaticallyOption, nil];
NSError *error = nil;
if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
[NSException raise:#"Open failed" format: #"Reason: %#", [error localizedDescription]];
}
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:psc];
[context setUndoManager:nil];
}
return self;
}
-(NSString *) itemArchivePath
{
NSArray *docDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *dir = [docDirectories objectAtIndex:0];
return [dir stringByAppendingPathComponent:#"hsc1.data"];
}
and in every controller I get the singleton to perform operations. I plan on defining a delegate around round operations, and implementing it in my AppDelegate, so I'm only getting the service once in the app, but don't think that should matter for now....
Are you sure the data is actually corrupted? Managed object contexts are highly efficient, and it's normal to fault in the debugger. From the docs:
"Faulting is a mechanism Core Data employs to reduce your
application’s memory usage..."
See http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreData/Articles/cdFaultingUniquing.html
If the data is actually missing and cannot be accessed by other methods, make sure you're using the same Managed Object Context to access the data. If the data has not been committed to the data store, it will not "sync" between MOCs.
I'm having a real bad time with this core data error.
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'statement is still active'
My app and code all works fine except occasionally when calling requests very quickly. It happens when I'm trying to break the app.
Going from one screen to the next, downloading data and executing fetch requests.
I know it has something to do with threading and core data.
I'm calling this piece of code from a background thread, with it's own managed object context.
+ (AN_User *)updateWithRecord:(NSDictionary *)record moc:(NSManagedObjectContext *)moc{
NSNumber *userID = nil;
NSString *username = nil;
if([record objectForKey:#"user_id"]){
userID = [NSNumber numberWithInt:[[record objectForKey:#"user_id"] intValue]];
}else if([record objectForKey:#"id_member"]){
userID = [NSNumber numberWithInt:[[record objectForKey:#"id_member"] intValue]];
}
if([record objectForKey:#"username"]){
username = [NSString stringWithFormat:#"%#", [record objectForKey:#"username"]];
}else if([record objectForKey:#"member_name"]){
username = [NSString stringWithFormat:#"%#", [record objectForKey:#"member_name"]];
}
if(!userID||!username){
return nil;
}
__block AN_User *user = nil;
[moc performBlockAndWait:^{
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"AN_User" inManagedObjectContext:moc];
[request setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(user_id == %#) OR (username == %#)", userID, username];
[request setPredicate:predicate];
if([moc countForFetchRequest:request error:nil]==0){
user = (AN_User *)[NSEntityDescription insertNewObjectForEntityForName:#"AN_User" inManagedObjectContext:moc];
}else{
NSArray *fetchResults = [moc executeFetchRequest:request error:nil];
if(fetchResults.count>0){
user = [fetchResults objectAtIndex:0];
}
}
if(user){
user.user_id = userID;
user.username = username.lowercaseString;
//Parse profile image url
NSString *avatar = [record objectForKey:#"avatar"];
NSString *fileName = [record objectForKey:#"filename"];
if([avatar isKindOfClass:[NSString class]]&&avatar.length>0){
user.profile_image_url = [NSString stringWithFormat:#"%#", avatar];
}else if([fileName isKindOfClass:[NSString class]]&&fileName.length>0){
user.profile_image_url = [NSString stringWithFormat:#"http://www.example.com/forum/avs/%#", fileName];
}
if([record objectForKey:#"gpbp_respect"]){
user.respect = [NSNumber numberWithFloat:[[record objectForKey:#"gpbp_respect"] floatValue]];
}
}
}];
return user;
}
I understand it's probably hard to tell from just this, but can anyone tell me if I'm doing anything wrong, with these requests, that is immediately obvious.
If you scroll a table that calls core data on a b/g thread, it happens and Core Data expects to have the context all on one thread.
Another SO poster worked around this by creating a MOContext per thread, but I don't like the idea of CRUD on multiple threads so I just put a dispatch_async (dispatch_get_main_queue(), ) wrapping function around my code. So far no crashes, but it's rare so I am not absolutely certain on this.