Core Data Saving Attributes Of Entities - cocoa-touch

This question is about Core Data.
I created a Entity called TV with three attributes called name, price and size. I also created a subclass of NSMutableObject with TV.h and TV.m files.
I imported the TV.h to my DetailViewController.h which handles my sliders und UIElements I want to take the values of.
So I did a fetch request, and everything works fine, BUT:
Everytime I update the UISlider (valueDidChange:), Xcode creates a COPY of my entity and adds it to my TV-Object.
All I want Xcode is just to edit and save to the current entity, not to edit and save in a new entity.
Help is very appreciated!
Thank you in advance.
My Code:
DetailViewController.m
- (IBAction)collectSliderValue:(UISlider *)sender {
if (__managedObjectContext == nil) {
NSLog(#"Problem ...");
__managedObjectContext = [(MasterViewController *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSLog(#"... solved!");
}
if (sender == sizeSlider) {
NSError *error = nil;
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TV" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
TV * currentTV = [[TV alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
currentTV.size = [[NSNumber alloc] initWithInt:(sender.value + 0.5f)];
currentTV.name = #"New TV!";
NSError *error11;
[__managedObjectContext save:&error11];
for (NSManagedObject *info in fetchedObjects)
{
NSLog(#"Name = %#", [info valueForKey:#"name"]);
NSLog(#"Size = %#", [info valueForKey:#"size"]);
NSLog(#"Price = %#", [info valueForKey:#"price"]);
}
[fetchRequest release];
}

//Editing begins ...
TV * currentTV = [[TV alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
Editing doesn't begin, you are creating a new object right there. Your view controller needs an instance variable to hold the current TV entity that you are modifying.
From the template project you have created, the variable detailItem contains the managed object that you are currently editing. You should specifically set this as a TV object, and refer to this instead of currentTV in your detailViewController code. You must remove all of the fetch request and managed object context code - this is not relevant in your detail view controller, it should be managed by the master view controller.
So, in DetailViewController.h:
#property (strong, nonatomic) id detailItem;
becomes
#property (strong, nonatomic) TV detailItem;
And in your collectSliderValue method, it should look something much more simple like this:
- (IBAction)collectSliderValue:(UISlider *)sender
{
if (sender == sizeSlider)
self.detailItem.size = [NSNumber numberWithFloat:sender.value];
}
The saving of the managed object context shouldn't occur until back in your detail view controller, this is taken care of in your application delegate.
In your master detail controller .m file you may also need to import the TV.h file so that it knows that TV is a NSManagedObject subclass. Also, cast to TV when you are setting the detail item:
self.detailViewController.detailItem = (TV*)selectedObject;

Related

Memory not releasing when calling dealloc on NSFetchedResultsController

I am trying to debug a memory leak within my code (2.5mb) and it seems to be pointing to _prepareResultsFromResultSet within NSFetchedResultsController. I have 2 viewControllers (A & B), viewController B is pushed on to the navigationController from viewController A.
In B I am performing an NSFetchRequest using NSFetchedResultsController as follows:
.h
#property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
#property (nonatomic, retain) NSString *sMapSlug;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andMapSlug: (NSString *)slug;
.m
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil andMapSlug: (NSString *)slug
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
self.sMapSlug = [NSString stringWithString:slug];
}
return self;
}
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil)
return _fetchedResultsController;
Singleton *singleton = [Singleton sharedSingleton];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"DescriptionEntity" inManagedObjectContext:[singleton managedObjectContext]];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:#"map.sName" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
NSPredicate *myPredicate = [NSPredicate predicateWithFormat:#"map.sSlug LIKE %#", self.sMapSlug];
[fetchRequest setFetchBatchSize:8];
[fetchRequest setPredicate:myPredicate];
// Finally check the results
NSError *error;
NSArray *fetchedObjects = [[singleton managedObjectContext] executeFetchRequest:fetchRequest error:&error];
for (DescriptionEntity *desc in fetchedObjects)
{
NSLog(#"Maps present in database: %#", desc.map.sName);
}
NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[singleton managedObjectContext] sectionNameKeyPath:nil cacheName:#"Test1"];
self.fetchedResultsController = theFetchedResultsController;
self.fetchedResultsController.delegate = self;
//[self.fetchedResultsController performFetch:NULL];
[theFetchedResultsController release];
[fetchRequest release];
[sort release];
return _fetchedResultsController;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
[self.fetchedResultsController fetchRequest];
}
Everything is loaded in fine using the fetchRequest and I can access my data if need be, however I have taken this out for now so I know none of the data from the NSFetchedRequestController is being used. When I go back on the navigation stack the dealloc for controller B is called, and I perform the following:
- (void)dealloc {
self.fetchedResultsController.delegate = nil;
[_fetchedResultsController release];
[sMapSlug release];
[super dealloc];
}
When I take heap shots of this within Instruments I see the following data stay after the dealloc is called:
If I go back in to controller B again, another set of this memory is added and never removed. However, if I do this a 3rd time the amount does not increment. I am presuming that there is some sort of caching going on, can anyone tell me how to remove this data or how to correctly deallocate an NSFetchedResultsController.
If you need any more information feel free to ask.
Usually you should not worry about memory management in Core Data. Yes, under the hood a caching mechanism is involved.
Anyway, there are two different methods to "dismiss" the memory.
The first is refreshObject:mergeChanges: method. Passing to it NO, it allows to prune the object graph. In other words, it throws away any changed data that has not been saved to the store.
Override willTurnIntoFault and didTurnIntoFault to see it in action.
The other is to call reset on a managed context. Obviosly, this removes all the objects the context contains.
Hope that helps.
You should never ever call dealloc yourself, not even on your own objects. This is something the system does it self.

NSManagedObjectContext returns nil in Core Data app (iOS)

I'm trying to save a string into a database every time a button is pressed but when I run the project, I get that on my console: 'NSInvalidArgumentException', reason: '+entityForName: nil is not a legal NSManagedObjectContext parameter searching for entity name 'Info''.
Referring to the Data Model, I have created a .xcdatamodeld with an Entity named 'Info' and, inside it, an attribute named 'path' with a type of string.
I've created three functions. "enterdata" Checks if the name is avaliable or not by calling "findData". If the name is avaliable, a new data is recorded throught "newData", if not, it looks for a different name.
I've been looking for some similar questions and I've found out this. It says that de ManagedObjectContext has to be passed to the View Controller but I don't understand what does it mean.
Here's my .h code:
#property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
Here's my .m code:
#import <CoreData/CoreData.h>
#synthesize managedObjectContext;
int iSavedNum = 1;
bool bCanSave;
//Enter data
- (IBAction) enterdata:(id)sender {
//Search if data is already registered
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path = [NSString stringWithFormat:#"%#/info%i.png",docDir, iSavedNum];
[self findData:path :#"path"];
//If data is already saved, save it with new name.
if (bCanSave == NO) {
for (iSavedNum = 1; bCanSave == YES; iSavedNum++) {
[self findData:path :#"path"];
if (bCanSave == YES) {
[self newData:path :#"path"];
}
}
} else {
[self newData:path :#"path"];
}
}
//Input new data
- (void) newData:(NSString *)value:(NSString *)key {
//Create ManagedObjectContext and ManagedObjectModel
__0AppDelegate *appDelegate = (__0AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObjectModel *newRecord;
//Put the data to the Entity
NSString *entityName = #"Info";
newRecord = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
[newRecord setValue:value forKey:key];
//Errors management and cheking
NSError *error;
[context save:&error];
NSLog(#"Info Saved. Value: %# Key: %#", value, key);
}
//Find Data
- (void) findData:(NSString *)valor:(NSString *)key {
//Create ManagedObjectContext
__0AppDelegate *appDelegate = (__0AppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
//Call the Entity and make a request
NSString *entityName = #"Info";
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:entityName inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
//Create predicate to call specific info
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(%# = %#)", key, valor];
[request setPredicate:pred];
//Errors management and creation of an array with found info
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
//Set if the name is avaliable or not
if ([objects count] == 0) {
bCanSave = YES;
} else {
bCanSave = NO;
}
}
It tells you exactly what the error is:
nil is not a legal NSManagedObjectContext parameter
That means that on this line:
newRecord = [NSEntityDescription insertNewObjectForEntityForName:entityName
inManagedObjectContext:context];
The variable context is nil. This means that your managedObjectContext method isn't working correctly. You don't show this so there's not much more we can add.
In application:didFinishLaunchingWithOptions: in appDelegate
/*initiate the managed Object Context */
CoreDataManager *coreDataManager = [CoreDataManager sharedDataManager];
coreDataManager.managedObjectContext = self.managedObjectContext;
where CoreDataManager is my core date manager which explicitly contains all the core data save, delete methods
Or
yourClassObject.managedObjectContext = self.managedObjectContext;
so context get initialized

store result from wcf service in core data

I am using a WCF service in my app.When the app is run for the first time on the iPad,I want it to call a WCF service and display the result in a UITableView.Alongwith displaying the data in UITableView,i want to store the data in Core Data so when the user is "offline"(not connected to wifi)the data will be displayed from the Core Data.The AppDelegate.m looks like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (![defaults objectForKey:#"firstRun"])
{
self.firstRun = TRUE;
[defaults setObject:[NSDate date] forKey:#"firstRun"];
}
else
{
self.firstRun = FALSE;//flag does exist so this ISNT the first run
}
[[NSUserDefaults standardUserDefaults] synchronize];
}
The code in UITableView looks like this:
- (void)viewDidLoad
{
[super viewDidLoad];
[my_table setDataSource:self];
[my_table setDelegate:self];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if (appDelegate.firstRun){
NSLog(#"IS FIRST RUN");
EDViPadDocSyncService *service = [[EDViPadDocSyncService alloc]init];
[service getAllCategories:self action:#selector(handleGetAllCategories:)];
}
else
{
NSLog(#"NOT FIRST RUN");
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Categories" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSError *errormsg;
self.allCats = [managedObjectContext executeFetchRequest:fetchRequest error:&errormsg];
NSLog(#"allCATS=%#",self.allCats);
self.title = #"Categories";
}
}
-(void)handleGetAllCategories:(id)value
{
if([value isKindOfClass:[NSError class]])
{
NSLog(#"This is an error %#",value);
return;
}
if([value isKindOfClass:[SoapFault class]])
{
NSLog(#"this is a soap fault %#",value);
return;
}
NSMutableArray *result = (NSMutableArray*)value;
NSMutableArray *categoryList = [[NSMutableArray alloc] init];
NSMutableArray *docCount = [[NSMutableArray alloc]init];
NSMutableArray *catIdList = [[NSMutableArray alloc]init];
self.myData = [[NSMutableArray array] init];
self.myDocCount = [[NSMutableArray array]init];
self.catId = [[NSMutableArray array]init];
for (int i = 0; i < [result count]; i++)
{
EDVCategory *catObj = [[EDVCategory alloc]init];
catObj = [result objectAtIndex:i];
[categoryList addObject:[catObj categoryName]];
[docCount addObject:[NSNumber numberWithInt:[catObj docCount]]];
[catIdList addObject:[NSNumber numberWithInt:[catObj categoryId]]];
}
self.myData = categoryList;
self.myDocCount = docCount;
self.catId = catIdList;
[my_table reloadData];
/*store data in Core Data - START*/
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *newCategory;
for(int j=0;j<[result count];j++)
{
newCategory = [NSEntityDescription insertNewObjectForEntityForName:#"Categories" inManagedObjectContext:context];
/*HOW TO STORE DATA FOR THE "CATEGORIES" OBJECT IN CORE DATA*/
}
/*store data in Core Data - END*/
}
I am not able to figure out how to store the data received from the wcf service to the core data object directly.I know how to store it from a text box on the screen to a core data object.eg.:-
coreDataAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *newContact;
newCat = [NSEntityDescription insertNewObjectForEntityForName:#"Categories" inManagedObjectContext:context];
[newCat setValue:name.text forKey:#"name"];
name.text = #"";
[context save:&error];
But this doesn't help in my case.Any help is appreciated.
You are mixing networking and UI code. It is a recipe for unmaintainable code.
Your UI should be looking at Core Data and only Core Data to display its data.
Separately, and asynchronously you should be requesting data from WCF and pushing it into Core Data.
Your UI does not need to care about first run vs. subsequent run. It just looks at Core Data via a NSFetchedResultsController.
Your network code is the only part that cares about new vs. update.
Update 1
how can I achieve this? When the app is running and connected to WiFi,it has to get the latest data from the WCF service.
NSURLConnection can do async requests built-in. I generally recommend writing your networking code as NSOperation subclasses and then put them into a queue.
It appears that WCF can return XML and takes standard HTTP requests. Therefore you can write NSOperation subclasses that build your request, send it to the server and wait for a reply. When the reply comes you parse the XML and insert it into Core Data. When you save the Core Data NSManagedObjectContext your NSFetchedResultsController instances will automatically fire and allow you to update your UI.
I have several code samples that perform these feats although they are written for JSON responses as opposed to XML responses. It would not be difficult to take those examples and alter them to your needs.
You can start with this stackoverflow question and its response.
To store the data into the attributes of your NSManagedObject, simply set the values using KVC:
EDVCategory *catObject = [result objectAtIndex:j];
[newCategory setValue:[catObject categoryName] forKey#"categoryName"];
[newCategory setValue:[catObject docCount] forKey#"docCount"];
[newCategory setValue:[catObject categoryID] forKey#"categoryID"];
// after the loop
[context save:&nil];

NSTokenFieldCell Subclass to force use of Core Data To-Many Relationship

I have come across an interesting conundrum (of course, I could just being doing something horribly wrong).
I would like an NSTokenField to "represent" a relationship in a Core Data Application. The premise is such: You click on a Note from a TableView (loaded from the Notes Array Controller). The token field is then bound (through "value") to the Notes Array Controller selection.Tags. Tags is a to-many relationship on the entity Notes.
Obviously, an NSTokenField will not accept the NSSet that the Array Controller Provides it. To get around this, I subclassed NSTokenFieldCell and overrode its objectValue and setObjectValue: methods. I thought that I could simply translate the NSSet that was being provided to the NSArray that the NSTokenFieldCell expected. (Note: I originally tried overriding these methods on a NSTokenField subclass; however, they were not being called.)
So, I came up with said code:
- (void)setObjectValue:(NSSet*)object {
tagsList = [object copy];
NSMutableArray *displayList = [[NSMutableArray alloc] init];
for (id newObject in tagsList) {
[displayList addObject:[newObject valueForKey:#"Name"]];
}
[super setObjectValue:displayList];
}
- (id)objectValue {
NSArray *displayList = [super objectValue];
NSEntityDescription *tagEntity = [NSEntityDescription
entityForName:#"Tag"
inManagedObjectContext:[appDelegate
managedObjectContext]];
NSMutableSet *returnValue = [[NSMutableSet alloc] init];
for (NSString *token in displayList) {
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:tagEntity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"Name == %#", token];
[request setPredicate:predicate];
NSError *error;
NSArray *results = [[appDelegate managedObjectContext] executeFetchRequest:request error:&error];
if (results == nil) {
NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:#"Tag" inManagedObjectContext:[appDelegate managedObjectContext]];
[object setValue:token forKey:#"Name"];
[returnValue addObject:object];
} else {
[returnValue addObject:[results objectAtIndex:0]];
}
}
return returnValue;
}
It crashes. :( And, surprisingly it crashes on the line that calls [super objectValue]. It gives me the error:
-[NSConcreteAttributedString countByEnumeratingWithState:objects:count:]: unrecognized selector sent to instance ...
Sigh. The sad thing is that when I go into the Core Data XML file and give the Note a Tag, it displays correctly, and [super setObjectValue:] is passed an array of strings. However, as soon as I enter something else and mouse away, I get the error.
I am not sure what to do about this. Can anyone spot anything horribly wrong with this? Thanks.
UPDATE:
If it makes a difference, I do not have a delegate configured for the TokenField.
In typical SO fashion, I found the answer to my own question. It was silly to begin with. I simply needed another ArrayController bound to the Notes selection.Tags set. Then, I bound the NSTokenField to the ArrangedObjects of that Controller, implemented some delegate methods. Boom. Simple.
Silly me.

Removing a binary attribute's data(used in NSImageView) in Core Data entity

I have an optional binary attribute: image, containing an image for my entities.
In the interface, I have NSImageView (Image Well), and a "Remove Image" button. When the image removing button is clicked, I do:
- (IBAction)saveAction:(id)sender {
NSError *error = nil;
if (![[self managedObjectContext] save:&error]) {
[[NSApplication sharedApplication] presentError:error];
}
[tableView reloadData];
}
- (IBAction)removeImage:(id)sender {
[image setImage:nil]; // image is a NSImageView outlet bound to the image attribute.
[self saveAction:sender];
}
It clears the image from the NSImageView, but the binary data is still retained in the Core Data entity.
How do I reflect the change in the Core Data entity as well?
Thanks!
Edit:
NSImageView is already bound to model's image attribute, and available as outlet too. So I'm just looking for someone to tell me how to reset the attribute by fetching the model (if that's what I need to do).
Would appreciate any code help. :)
[image setImage:nil];
Is image actually an image view? If so, I must remind you to name your instance variables clearly and accurately.
You need to set the image property of the model object(s), not the view(s). Bind the views through the controllers to the model; then, when you change the model, the views pick up the changes for free.
I was under impression that altering an array from a fetch request won't make a difference to the actual data in storage. But I was wrong. I tried and it worked! Thanks Peter, and everyone elsewhere!
Here's what I replaced my image removal function for currently selected entity having a unique attribute:
- (IBAction)removeImage:(id)sender {
// Fetch the entity in question.
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObjectModel *model = [self managedObjectModel];
NSEntityDescription *entity = [[model entitiesByName] valueForKey:#"myEntity"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"unique_attr == %#", [unique_attr_outlet stringValue]];
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
[fetch setEntity:entity];
[fetch setPredicate:predicate];
// Load it into NSArray object and remove the binary data attribute.
NSArray *contextArray = [context executeFetchRequest:fetch error:nil];
if ([contextArray count] > 0)
[[contextArray objectAtIndex:0] setValue:nil forKey:#"myImage"];
[fetch release];
}