I'm using Core Data and I need to loop thru the result of the request, create several custom objects in the loop and store them in a NSMUtableArray, so I can send it to another view to feed a UI component. This is what I'm doing:
NSMutableArray *persons = [[NSMutableArray alloc] init];
NSError *error = nil;
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Person" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *info in fetchedObjects) {
ToggleButtonInfo *btn = [[ToggleButtonInfo alloc] init];
NSString *personName = [NSString stringWithFormat:#"ww %#", [info valueForKey:#"name"]];
NSLog(#"pn: %#", personName);
[btn setButtonInfo:personName];
[persons addObject:btn];
}
[fetchRequest release];
return persons;
The loop is working just fine, the information is there. The problem is that I get a "EXC_BAD_ACCESS" in my component if I use:
[info valueForKey:#"name"]
if I do something like this:
[btn setButtonInfo:#"something else here"];
everything works fine. So it looks like info is been de-allocated and that is causing the error, right? I try creating the scring using stringWithFormat but it doesn't work, same error.
An ideas?
Where do you get the EXC_BAD_ACCESS? I assume it's later when you're displaying the button? -setButtonInfo: probably isn't retaining, or you're over-releasing somewhere else.
Note that you're leaking btn in this code.
Related
This has been bugging me for 2 days now and I cant get my head around it. I want to extract several images that are stored in my core data store. The images are user selected using the image picker and rendered to a size of less then 70KB, transformed to data and popped into the core data store. The images are to be used in a slideshow. Entity is called ImageCD and the image is stored in the attribute friendsPhoto. I also allocate a date attribute to sort the images. This is done when the image is saved and is also an attribute of ImageCD.
I am using the following code to TRY and set the array but it does not return anything. im not getting a (NULL) readout on the debugger just that "Number of elements in array = 0;"
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:&sortDescriptor count:1];
NSLog(#"array's content sortDe:%#",sortDescriptors);
NSMutableArray* sortedImages = [[NSMutableArray alloc] initWithObjects:imageCD.friendsPhoto, nil];
[sortedImages sortUsingDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];
NSLog(#"array's content:%#",sortedImages);
imageArray = [[NSMutableArray alloc]init] ;
self.imageArray = sortedImages;
NSLog (#"Number of elements in array = %i", [imageArray count]);
self.theImageView.animationImages = [[NSArray alloc] initWithArray:imageArray];
NSLog(#"animationImages:%#",self.theImageView.animationImages);
So, what am I doing wrong? I have my suspicions it is something to do with this line of code...
NSMutableArray* sortedImages = [[NSMutableArray alloc] initWithObjects:imageCD.friendsPhoto, nil];
But cant work out what the alternate should be. I will be forever indebted to whoever can correct my mistake. I thank you in advance and please, let me know if you require any further information, a beer or a bacon sandwich.
Ohh, to save my sanity, I sanity checked the images by adding a class from my previous app (My Outfit) which utilizes this approach to retrieving images from core data BUT within a tableView, so called in cellForRowInIndexPath etc. and all the images are sound.
DetartrateD
2011-08-04 19:40:33.051 My Class Book[1027:707] After managedObjectContext: <NSManagedObjectContext: 0x1821b0>
2011-08-04 19:40:33.057 My Class Book[1027:707] array's content sortDe:(
"(date, ascending, compare:)"
)
2011-08-04 19:40:33.059 My Class Book[1027:707] array's content:(
)
2011-08-04 19:40:33.061 My Class Book[1027:707] Number of elements in array = 0
2011-08-04 19:40:33.063 My Class Book[1027:707] animationImages:(
)
Now fixed with these changes
...
NSMutableArray *mutableFetchResultsA = [[managedObjectContext executeFetchRequest:requestA error:&error] mutableCopy];
if (mutableFetchResultsA == nil) {
NSLog(#"Testing: No results found");
}
else {
NSLog(#"Testing: %d Results found.", [mutableFetchResultsA count]);
NSLog(#"array's content sortDe:%#",mutableFetchResultsA);
}
[sortDescriptor release];
[sortDescriptors release];
imageArray = [[NSMutableArray alloc]init] ;
self.imageArray = [mutableFetchResultsA valueForKey:#"friendsPhoto"];
[mutableFetchResultsA release];
[requestA release];
...
Assuming that the images are stored as NSData * attributes in the ImageCD entity, here is what you need to do:
self.imageArray = [self getImagesInManagedObjectContext:self.managedObjectContext];
Here is the getImagesInManagedObjectContext: method. This method retrieves from the Core Data store the ImageCD objects, sorted using your attribute as required, then creates and returns an autoreleased NSMutableArray of UIImage * objects or nil if no objects have been found.
- (NSMutableArray *) getImagesInManagedObjectContext:(NSManagedObjectContext *)moc{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"ImageCD" inManagedObjectContext:moc];
[request setEntity:entity];
// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"date" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSMutableArray *array = [NSMutableArray arrayWithArray: [moc executeFetchRequest:request error:&error]];
[request release];
[sortDescriptor release];
[sortDescriptors release];
if (array && [array count] && !error){
// create an array of images
NSMutableArray *images = [[NSMutableArray alloc] init];
for(ImageCD *obj in array){
UIImage *img = [UIImage imageWithData: (NSData *) obj.friendsPhoto];
[images addObject:img];
}
return [images autorelease];
}
else
return nil;
}
I have the following code that fetches a Session object for a particular Exercise object.
This fetch loads data into my UITableView.
The count is fine, I just need a way to extract the Session.timeStamp property so I can set it to UITableViewCell's textLabel property.
Does anyone know how?
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"name == %#", exercise.name]];
NSEntityDescription *sessionEntity = [NSEntityDescription entityForName:#"Exercise" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:sessionEntity];
NSError *error = nil;
NSArray *results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
NSMutableArray *mutableSessionArray = [NSMutableArray array];
for (Exercise *ex in results) {
Session *session = [ex exercises];
[mutableSessionArray addObject:session];
}
self.sessionArray = [NSArray arrayWithArray:mutableSessionArray];
Call timestamp property of your mananged object subclass or call valueForKey.
Hey, I've created a custom retrieval method for database access:
+(NSArray*) recordsForTable:(NSString *)table predicate:(NSPredicate *)prd{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:table inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:prd];
NSArray *records = [managedObjectContext executeFetchRequest:fetchRequest error:nil];
[fetchRequest release];
return records;
}
i then use the above method in this method:
-(NSArray *)tableViewControllerData{
NSNumber *savedBool = [[NSNumber alloc] initWithBool:YES];
NSString *onlyGetSavedVisitObjects = [NSString stringWithFormat:#"bolSaved=%#", savedBool];
[savedBool release];
NSMutableArray *data = [[[CoreDataAccess recordsForTable:#"LPVisit" stringPredicate:onlyGetSavedVisitObjects] mutableCopy] autorelease];
NSSortDescriptor *dateDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"dteVisitDate" ascending:NO];
NSArray *descriptors = [NSArray arrayWithObjects:dateDescriptor, nil];
[data sortedArrayUsingDescriptors:descriptors];
return data;
}
The trouble am having is that when the user makes changes to the LPVisit table and recall this method to show those changes it crashes the application.
[EDIT]
The exception it produces is:
-[__NSArrayM objectID]: unrecognized selector sent to instance 0x4dac1f0
I believe the error is at line:
NSMutableArray *data = [[[CoreDataAccess recordsForTable:#"LPVisit" stringPredicate:onlyGetSavedVisitObjects] mutableCopy] autorelease];
If I remove the autorelease, I get a memory leak but the application doesn't crash.
Does anyone have any insights, thanks in advance
Is it possible that the mutable copying is throwing the exception because *records is nil? This might happen if #"bolSaved=%#" is a typo and should be #"boolSaved=%#".
I have been slowly learning iPhone development and seem to keep hitting walls where I can't figure out how to do what I want to do the right way :(
Basically, I want a class that handles all interactions with the data layer, for example, getting a mutable array of some list of objects from the data store.
This is pretty trivial in other languages where you have a garbage collector, but in Objective-C on the iPhone, I'm not sure what to do.
This is an example method on a DataFactory class we were creating. Note the comment on where we are not sure when to release....
- (NSMutableArray*)fetchAllDrivers{
NSMutableArray *results = [[NSMutableArray alloc] init];;
if (self.appDelegate != nil) {
NSManagedObjectContext *context = [self.appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
[request setEntity: entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor, nil];
[request setSortDescriptors: sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
NSError *error;
results = [[context executeFetchRequest:request error:&error] mutableCopy];
if (results == nil) {
//something went wrong
}
//Where should this be released??? Certainly not here!
[results release];
[request release];
}
else {
[NSException raise:#"Can't fetch b/c app delgate is nil!" format: #"!!!"];
}
return results;
}
Calling code, related to my comment:
NSMutableArray* arr = [dataFactory fetchAllDrivers];
[arr retain];
//Some code where we use arr
[arr release];
Following naming conventions, your fetchAllDrivers should return an autoreleased object.
- (NSMutableArray*)fetchAllDrivers
{
if (!self.appDelegate) {
// Big Problems Raise exception immediately if you want...
return nil;
}
NSManagedObjectContext *context = [self.appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Person" inManagedObjectContext:context];
[request setEntity: entity];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"lastName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor, nil];
[request setSortDescriptors: sortDescriptors];
[sortDescriptors release];
[sortDescriptor release];
NSError *error = nil;
NSMutableArray *results = [[NSMutableArray alloc] initWithArray:[context executeFetchRequest:request error:&error] copyItems:YES];
if (error) {
// Something went wrong
[results release];
// Error handling code here
[request release];
return nil;
}
[request release];
return [results autorelease];
}
NSMutableArray* arr = [dataFactory fetchAllDrivers];
[arr retain];
//Some code where we use arr
[arr release];
By convention, any object returned from the method of an external object is autoreleased. You don't need to retain them except in properties. If you only using arr in the local scope of the method then you don't need to retain/release it. It is autoreleased and will die after the end of the local scope.
If you need to have arr hang around inside the object. You should store it in a retained property:
#property (nonatomic,retain) NSMutableArray *arr;
... then use it with the self notation to ensure retention:
self.arr=[dataFactory fetchAllDrivers];
... then you need only release it in the class' dealloc method.
Having one object manage your data model is very good idea but it is not a "factory". Objective-c does not use factories like C++ and similar languages. Trying to think in those terms will lead to grief. The object should instead be thought of as a "controller" or "manager".
I've got a basic photo album application, on the first view a list of albums is displayed with a subtitle showing how many images are in each album. I've got everything working to add albums, and add images to albums.
The problem is that the image count lines are accurate whenever the app loads, but I can't get them to update during execution.
The following viewdidload correctly populates all lines of the tableview when the app loads:
- (void)viewDidLoad {
[super viewDidLoad];
// Set the title.
self.title = #"Photo albums";
// Configure the add and edit buttons.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(addAlbum)];
addButton.enabled = YES;
self.navigationItem.rightBarButtonItem = addButton;
/*
Fetch existing albums.
Create a fetch request; find the Album entity and assign it to the request; add a sort descriptor; then execute the fetch.
*/
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Album" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
// Order the albums by name.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"albumName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
}
LocationsAppDelegate *mainDelegate = (LocationsAppDelegate *)[[UIApplication sharedApplication] delegate];
// Set master albums array to the mutable array, then clean up.
[mainDelegate setAlbumsArray:mutableFetchResults];
[mutableFetchResults release];
[request release];
}
But when I run similar code inside viewdidappear, nothing happens:
{
/*
Fetch existing albums.
Create a fetch request; find the Album entity and assign it to the request; add a sort descriptor; then execute the fetch.
*/
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Album" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
// Order the albums by creation date, most recent first.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"albumName" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
[sortDescriptors release];
// Execute the fetch -- create a mutable copy of the result.
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
}
LocationsAppDelegate *mainDelegate = (LocationsAppDelegate *)[[UIApplication sharedApplication] delegate];
// Set master albums array to the mutable array, then clean up.
[mainDelegate setAlbumsArray:mutableFetchResults];
[self.tableView reloadData];
[mutableFetchResults release];
[request release];
}
Apologies if I've missed the answer to this question elsewhere, but what am I missing?
To troubleshoot, put NSLog statements in a few places in -viewDidAppear:. You would use these log statements to make sure that this method is both being called properly and to examine the contents of mutableFetchResults, at least.