Core Data data access pattern? - iphone

It seems crazy to me that I have all of these NSFetchRequests for the same NSManagedObjects spread out throughout different view controllers in my app, is there a good pattern for data access that puts what I need in a single place?

I agree it is a bit much, fortunately there is Active Record for Core Data. This makes fetching less tedious, for example, fetching all Person objects from core data would be as simple as
NSArray *people = [Person findAll];

Yes there is, it is called a facade pattern. Simply define a public method on your NSManagedObject subclass like so:
#interface Group : NSManagedObject { }
// … cruft here …
-(NSArray*)peopleSortedByName;
#end
And hide the nasty implementation like so:
-(NSArray*)peopleSortedByName;
{
NSFetchRequest* request = // … bla bla, lots of code here
return [[self managedObjectContext] executeFetchRequest:request
error:NULL];
}
Then use the method just as if the it was any other class in your code. Write once, relief everywhere.

Define a category method for NSManagedObject context which wrappers up a general query into a one-liner.
#interface NSManagedObjectContext(MyQueryAdditions)
-(NSArray *)queryEntityForName:(NSString *)name predicateFormat:(NSString *)pstring argumentArray:(NSArray *)arr;
#end
#implementation NSManagedObjectContext(MyQueryAdditions)
-(NSArray *)queryEntityForName:(NSString *)name predicateFormat:(NSString *)pstring argumentArray:(NSArray *)arr
{
NSEntityDescription *entity = [NSEntityDescription entityForName:name inManagedObjectContext:self];
NSFetchRequest *fetch = [[[NSFetchRequest alloc] init] autorelease];
[fetch setEntity:entity];
NSPredicate *pred;
if(pstring)
{
if(arr) pred = [NSPredicate predicateWithFormat:pstring argumentArray:arr];
else pred = [NSPredicate predicateWithFormat:pstring];
[fetch setPredicate:pred];
}
NSError *error = nil;
[self retain];
[self lock];
NSArray *results = [self executeFetchRequest:fetch error:&error];
if (error) {
NSLog(#"MOC Fetch - Unresolved error %#, %#", error, [error userInfo]);
results = [NSArray array];
}
[self unlock];
[self release];
return results;
}
#end
Means a basic all items query can be as simple as
NSArray *cres = [managedObjectContext queryEntityForName:#"Person" predicateFormat:nil argumentArray:nil];

Related

Why is executeFetchRequest:fetchRequest leaking memory?

Instruments shows the following code leaks, if I comment out this code there is no leak.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
// Edit the entity name as appropriate.
NSEntityDescription *entity = [NSEntityDescription entityForName:USER_CORE_DATA inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *predicte = [NSPredicate predicateWithFormat:#"username == %#", [[User defaultManager] savedUsername]];
[fetchRequest setPredicate:predicte];
// set any predicates or sort descriptors, etc.
// execute the request
[self.managedObjectContext executeFetchRequest:fetchRequest onSuccess:^(NSArray *results) {
} onFailure:^(NSError *error) {
NSLog(#"Error fetching: %#", error);
}];
[fetchRequest release];
Specifically instruments says this line in the code above:
[self.managedObjectContext executeFetchRequest:fetchRequest onSuccess:^(NSArray *results)
It appears to be a leak with fetchRequest and/or the block. Any help will be appreciated, and thanks in advance.
It appears executeFetchRequest:onSuccess:onFailure: is a function you have defined in NSManagedObjectContext category. Ensure the NSArray object instance that you are passing to the onSuccess block is autoreleased.
Actually it turned out that StackMob had a leak in their code, I downloaded there source and fixed it.
- (NSString *)primaryKeyField
{
NSString *objectIdField = nil;
// Search for schemanameId
objectIdField = [[self SMSchema] stringByAppendingFormat:#"Id"];
if ([[[self entity] propertiesByName] objectForKey:objectIdField] != nil) {
return objectIdField;
}
objectIdField = nil; // This line was missing and causing a leak
// Search for schemaname_id
objectIdField = [[self SMSchema] stringByAppendingFormat:#"_id"];
if ([[[self entity] propertiesByName] objectForKey:objectIdField] != nil) {
return objectIdField;
}
objectIdField = nil; // This line was missing and causing a leak
// Raise an exception and return nil
[NSException raise:SMExceptionIncompatibleObject format:#"No Attribute found for `entity %# which maps to the primary key on StackMob. The Attribute name should match one of the following formats: lowercasedEntityNameId or lowercasedEntityName_id. If the managed object subclass for %# inherits from SMUserManagedObject, meaning it is intended to define user objects, you may return either of the above formats or whatever lowercase string with optional underscores matches the primary key field on StackMob.", [[self entity] name], [[self entity] name]];`

Core Data issue. Insert new NSManagedObject

I want to insert 200 5Mb records in my Core Database. But when I save the NSManagedObject, the memory wasn't released (autoreleased pool didn't help), and after inserting 30 records I got the memory warning and the application crashed. Here is my code
- (void)SaveItem
{
NSString *entityName = kEntityName;
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = appDelegate.managedObjectContext;
NSEntityDescription *entityDesctiption = [NSEntityDescription
entityForName: entityName
inManagedObjectContext:context];
// check if town exists
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id == %d", self.imageID];
NSFetchRequest *requestToCheckExistense = [[NSFetchRequest alloc] init];
[requestToCheckExistense setEntity:entityDesctiption];
[requestToCheckExistense setPredicate:predicate];
NSArray *objects = [context executeFetchRequest:requestToCheckExistense error:nil];
[requestToCheckExistense release];
if (objects == nil)
{
NSLog(#"there was an error");
}
NSManagedObject *object;
if ([objects count] > 0)
{
// edit item
object = [objects objectAtIndex:0];
}
else
{
// if object doesn't exist, find max id to imlement autoincrement
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesctiption];
request.propertiesToFetch = [NSArray arrayWithObjects: #"id", nil];
NSArray *allobjects = [context executeFetchRequest:request error:nil];
[request release];
NSInteger newID = 1;
if ([allobjects count] > 0)
{
NSNumber *maxID = [allobjects valueForKeyPath:#"#max.id"];
newID = [maxID intValue] + 1;
}
// write item
object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
[object setValue:[NSNumber numberWithInt:newID] forKey:#"id"];
self.imageID = newID;
}
// fill NSManagedObject
// size of objNSData is about 5MB
NSMutableData *objNSData = [[DatabaseManager sharedDatabaseManager] encryptedDataFromImage:bigImage];
[object setValue:objNSData forKey:#"big"];
[context save:nil];
}
When I commented
[object setValue:objNSData forKey:#"big"];
everything was OK.
I tried to add the code into #autoreleasepool , but that didn't help.
I know, that now, when I save data to database, it's still in iPhone RAM. How to release it from this memory? When I get a set of Managed Objects from the database, they are not in the RAM (I can easyly get 100 object, each of them has 5Mb fields)
object =(tblEntity *) [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
try to type cast the object,this may solve the problem
I've solved the issue.
after call of [self SaveItem];
I used
[context save];
[context reset];
[context save];
all the NSManagedObjects from the context will be released.
After that operation I can add as many big objects as I want
Because you don't own an NSManagedObject when you create it, it may be retained by the core data stack even after releasing it (when using an autoreleasepool contained inside the loop).
This may help:
Set the undo manager of your managedobjectContext to nil:
[context setUndoManager:nil];
Be sure that no properties of that object are retained anywhere, because then the managed object will not be released on time inside your loop.
Be sure to add an autorelease pool inside every loop execution, not wrapping all the loop itself, similar to:
for(i;i<n;i++) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[obj saveItem];
[pool drain];
}
If that object belongs to a hierarchy of NSManagedObjects, then you need to release the owner of this object too, for this one to be deallocated from memory.
You can check apple's documentation about memory management in CoreData.
Warning: big objects (> 1MB) are not recommended by Apple to be stored inside CoreData (Check this other question/answer.)

Generic method that checks entity existence in Core Data database

I want to write generic method that checks if a given entity is in a Core Data database. I would like to have one method that works for all entities. I came up with something like this:
-(BOOL)checkIfExistsEntity:(NSString *)entityName withFieldName:(NSString *)fieldName andFieldValue:(NSString *)value{
NSManagedObjectContext *managedObjectContext = [(FGuideAppDelegate *) [[UIApplication sharedApplication] delegate] managedObjectContext];
NSEntityDescription *selectEntityDescription = [NSEntityDescription
entityForName:entityName inManagedObjectContext:managedObjectContext];
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:selectEntityDescription];
NSPredicate *whereForFetch = [NSPredicate predicateWithFormat:#"%# = %#",fieldName, value];
[fetchRequest setPredicate:whereForFetch];
NSError *error = nil;
NSArray *array = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (array != nil && [array count] > 0){
return YES;
}else {
return NO;
}
}
However it looks like the string #"%# = %#" in the predicate I wrote is not parsed properly. Is there any way to implement described functionality without hardcoding entities properties in a predicate?
Check out dynamic property names in the link below.
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/Predicates/Articles/pCreating.html
Instead of %#, using %K should solve your problem
you need == not = (I think....)
You could use the countForFetchRequest method.

is this a bad Core Data pattern for sharing functionality?

I have an app and there is some basic functionality that needs to be shared across several controllers, one being adding and removing what I call BookmarkedObjects to my data store. So I created the following funciton and it seems to be working really well and I would like to apply this pattern to other function in my code like adding objects to an order, seeing if an order already exist ect..
Here is what I am doing:
+(void) RemoveBookmark: (NSString *) aItemID withCustomerNumber: (NSString *) aCustomerNumber withManufacturerID: (NSString *) aManufacturerID withManagedObjectContext: (NSManagedObjectContext *) aContext {
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"BookmarkedItem" inManagedObjectContext:aContext]];
[request setPredicate:[NSPredicate predicateWithFormat:#"CustomerNumber==%# AND ManufacturerID==%# AND ItemID==%#", aCustomerNumber, aManufacturerID, aItemID]];
[request setFetchLimit:1];
NSError *error = nil;
NSArray *results = [aContext executeFetchRequest:request error:&error];
[request release], request = nil;
[aContext deleteObject:[results lastObject]];
[aContext save:&error];
}
I would suggest you to make a singleton class and add all such type of function in it.The main benefit of its is that all class methods are loaded on compile time so if there are 5 such functions the your app will take up unnecessary memory, so I'll suggest you something
like this
Define a class named SharedFunctions
in .h
#interface SharedFunctions : NSObject
{
}
+(SharedFunctions*)sharedInstance;
-(void)yourFunction;
.....
//make as many instace methods as you want
#end
in .m
#implementation SharedFunctions
static SharedFunctions* singletonInstance = nil;
-(id)init
{
if (self = [super init])
{
}
return self;
}
+(SharedFunctions*)sharedInstance
{
#synchronized(self) {
if (singletonInstance == nil)
{
singletonInstance = [[SharedFunctions alloc] init];
}
}
return singletonInstance;
}
///and define your methods
-(void)yourFunction
{
//somthing
}
#end
Now to use your methods just call
[[SharedFunctions sharedInstance] yourFunction];
By this way all such methods are at one place and there is only one class method so no memory issue.

How to store array of NSManagedObjects in an NSManagedObject

I am loading my app with a property list of data from a web site. This property list file contains an NSArray of NSDictionaries which itself contains an NSArray of NSDictionaries. Basically, I'm trying to load a tableView of restaurant menu categories each of which contains menu items.
My property list file is fine. I am able to load the file and loop through the nodes structure creating NSEntityDescriptions and am able to save to Core Data. Everything works fine and expectedly except that in my menu category managed object, I have an NSArray of menu items for that category. Later on, when I fetch the categories, the pointers to the menu items in a category is lost and I get all the menu items. Am I suppose to be using predicates or does Core Data keep track of my object graph for me?
Can anyone look at how I am loading Core Data and point out the flaw in my logic? I'm pretty good with either SQL and OOP by themselves, but am a little bewildered by ORM. I thought that I should just be able to use aggregation in my managed objects and that the framework would keep track of the pointers for me, but apparently not.
NSError *error;
NSURL *url = [NSURL URLWithString:#"http://foo.com"];
NSArray *categories = [[NSArray alloc] initWithContentsOfURL:url];
NSMutableArray *menuCategories = [[NSMutableArray alloc] init];
for (int i=0; i<[categories count]; i++){
MenuCategory *menuCategory = [NSEntityDescription
insertNewObjectForEntityForName:#"MenuCategory"
inManagedObjectContext:[self managedObjectContext]];
NSDictionary *category = [categories objectAtIndex:i];
menuCategory.name = [category objectForKey:#"name"];
NSArray *items = [category objectForKey:#"items"];
NSMutableArray *menuItems = [[NSMutableArray alloc] init];
for (int j=0; j<[items count]; j++){
MenuItem *menuItem = [NSEntityDescription
insertNewObjectForEntityForName:#"MenuItem"
inManagedObjectContext:[self managedObjectContext]];
NSDictionary *item = [items objectAtIndex:j];
menuItem.name = [item objectForKey:#"name"];
menuItem.price = [item objectForKey:#"price"];
menuItem.image = [item objectForKey:#"image"];
menuItem.details = [item objectForKey:#"details"];
[menuItems addObject:menuItem];
}
[menuCategory setValue:menuItems forKey:#"menuItems"];
[menuCategories addObject:menuCategory];
[menuItems release];
}
if (![[self managedObjectContext] save:&error]) {
NSLog(#"An error occurred: %#", [error localizedDescription]);
}
You set a NSArray as to-many relationship object
NSMutableArray *menuItems = [[NSMutableArray alloc] init];
[menuCategory setValue:menuItems forKey:#"menuItems"];
which might cause the trouble.(should throw an exception?) Relationships in CoreData are always unsorted, therefore NSSets. Add a sortIndex property to your entities for ordering.
I had the same issue. There are 2 major problems with using NSSets and Core Data: if you need non-distinct objects and need them ordered. As an example, say you have 2 entities in Core Data: professor and student. The student takes 10 classes for a degree program and you wish to have a (one-to-many) relationship from the student to the professor in order that the classes were taken. Also, the same professor may teach more than one class. This was how I overcame the issue. Create a Binary Data attribute (we'll call it profData) in student and store dictionaries that make it possible to reconstruct the data as needed. Note: don't store an array of professors, since they inherit from NSManagedObject vs. NSObject. That can cause problems. You can bolt on the required methods using a category. In this example, I created a category on Student called ProfList (Student+ProfList.h/m). This keeps the code out of the NSManagedObject subclasses, so if my attributes in Core Data change, I can regenerate the subclasses automatically without wiping out this code. Here is some sample code:
// Student+ProfList.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "Student.h"
#import "Professor.h"
#interface Student (ProfList)
- (NSArray *)getStudentsFullList;
- (void)storeStudentsFullList:(NSArray *)fullList;
#end
// Student+ProfList.m
#import "Student+ProfList.h"
#implementation Student (ProfList)
- (NSArray *)getStudentsFullList
{
NSData *storedData = self.profData;
if (!storedData) return nil;
NSMutableArray *fullList = [[NSMutableArray alloc] init];
// Retrieve any existing data
NSArray *arrayOfDictionaries = [NSKeyedUnarchiver unarchiveObjectWithData:storedData];
// Get the full professor list to pull from when recreating object array
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Professor"];
NSSortDescriptor *alphaSort =
[[NSSortDescriptor alloc] initWithKey:#"name"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
[fetchRequest setSortDescriptors:#[alphaSort]];
NSSet *allProfessors = [NSSet setWithArray:[context executeFetchRequest:fetchRequest error:nil]];
for (NSDictionary *dict in arrayOfDictionaries) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name LIKE %#", [dict objectForKey:#"name"]];
NSSet *filteredSet = [allProfessors filteredSetUsingPredicate:predicate];
Professor *newProfessor = [filteredSet anyObject];
newProfessor.index = [dict objectForKey:#"index"];
[fullList addObject:newProfessor];
}
return fullList;
}
- (void)storeStudentsFullList:(NSArray *)fullList
{
NSMutableArray *encodedList = [[NSMutableArray alloc] init];
for (Professor *professor in fullList) {
[encodedList addObject:#{#"index" : #([encodedList count]), #"name" : professor.name}];
}
NSArray *encodedArray = [NSArray arrayWithArray:encodedList];
NSData *arrayData = [NSKeyedArchiver archivedDataWithRootObject:encodedArray];
self.profData = arrayData;
}
#pragma mark - Core Data
- (NSManagedObjectContext *)managedObjectContext
{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
#end
You store a local variable in a view controller, then send this message to the student instance to get the list and save it locally for use in a table view or whatever.