iPad background thread NSAutoReleasePool confusion about [object release] - iphone

I have an app where I import a bunch of data and I want to to happen on a background thread. This was working well for a small amount of data but now it seems I am running into this error midway through the parsing and importing of large amounts of my data into Core Data:
Program received signal: “EXC_BAD_ACCESS”.
Here is the call to the background thread:
[self performSelectorInBackground:#selector(importAllData) withObject:nil];
Here is an example of what I am doing in my code:
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// load manufactuers from DB
NSFetchRequest *crequest = [[NSFetchRequest alloc] init];
NSEntityDescription *manufacturer = [NSEntityDescription entityForName:#"Manufacturer" inManagedObjectContext:managedObjectContext];
[crequest setEntity:manufacturer];
NSError *cerror=nil;;
NSArray *manufacturers = [[managedObjectContext executeFetchRequest:crequest error:&cerror]mutableCopy];
[crequest release];
for (int m=0; m < [manufacturers count]; m++) {
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:kClientListURL, [[manufacturers objectAtIndex:m]ManufacturerID]]];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setNumberOfTimesToRetryOnTimeout:2];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSString *responseString = [request responseString];
NSArray *items = [responseString JSONValue];
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
NSNumberFormatter *dec = [[NSNumberFormatter alloc]init];
[dec setNumberStyle:NSNumberFormatterDecimalStyle];
for (int i = 0; i < [items count]; i++)
{
Client *entity = (Client*) [NSEntityDescription insertNewObjectForEntityForName:#"Client" inManagedObjectContext:managedObjectContext];
[entity setCompanyName:[[items objectAtIndex:i] objectForKey:#"CompanyName"]];
// set a bunch of other properties
[entity setManufacturer:[manufacturers objectAtIndex:m]];
statusMessage = [NSString stringWithFormat:#"importing client: %#", entity.CompanyName];
[self performSelectorOnMainThread:#selector(setStatus) withObject:nil waitUntilDone:YES];
}
[f release];
[dec release];
} else {
NSLog(#"%#",[NSString stringWithFormat:#"JSON parsing failed: %#", [error localizedDescription]]);
}
NSError *entityerror;
if (![managedObjectContext save:&entityerror]) {
// //Handle the error.
NSLog(#"\n\n\n Error saving clients: %# \n\n\n\n",entityerror);
}
}
//More data importing code
[pool release];
As I said this was working fine for a small amount of data but now in he simulator it throws bad access errors at random points.
Then I read this article which confused me:
http://www.cocoadev.com/index.pl?DebuggingAutorelease
Do I not need to release my objects if they are inside of the NSAutoReleasePool? Am I causing them to be released twice at some point?
If I remove the release statements I get potential leaks errors when building using Command-Shift-A?

The rules are:
If you are the owner, you need to either call [object release] or [object autorelease]. The later will delegate the release call to the NSAutoReleasePool, when the pool gets flushed or released.
You are an owner if you called [object retain] or if you got the object through a method that has either alloc or copy in its name.
If you got the object from a method that has neither alloc nor copy in its name and you haven't retained it you must not call release or autorelease on it (except in the very rare case that the documentations states that you are the owner).
So yes, if you got an autoreleased object and you call release on it you are over-releasing it. The effects are various but almost always lead to a crash, normally with EXC_BAD_ACCESS. You can debug this by setting the NSZombieEnabled environment variable. That way, whenever an object is deallocated it is replaced by a dummy that knows what object lived at that address before and can then tell you which object got over-released.
But reading through your code I didn't spot any immediately obvious problem, but I don't know Core Data (which you seem to be using) well enough. Another possibility is a threading/timing issue. Also, you said that's not the whole method ? Maybe the error is in the missing part.

I am assuming that importAllData is the method you are using. If so, you are missing the : at the end in your call.
[self performSelectorInBackground:#selector(importAllData) withObject:nil];
should be
[self performSelectorInBackground:#selector(importAllData:) withObject:nil];
Hope that helps!

Related

valueForKey on data NSManagedObject not releasing memory

I want to deal with a data attribute, named originalImage in every Media entity in the store.
The problem is that despite the autoreleasing, memory builds up every time it's accessed via valueForKey, and eventually the app crashes. Or perhaps it's loading large individual NSData items which is the problem, but Instruments shows it to be a steadily inclining graph of memory usage, until it eventually gives me a memory warning and then crashes.
I haven't started on the rest of the code for this yet, so i'm not performing some hidden task that i'm not showing you.
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Media"
inManagedObjectContext:[self managedObjectContext]];
[request setEntity:entity];
[request setFetchBatchSize:10];
NSArray *mediaItems = [[self managedObjectContext] executeFetchRequest:request error:nil];
for (NSManagedObject *media in mediaItems) {
#autoreleasepool {
[media valueForKey:#"originalImage"];
}
}
EDIT: Today it seems that even just mentioning the NSManagedObject media is enough to cause this media leak. So even without the valueForKey line, i have a leak. I've tried this:
while (i < count) {
#autoreleasepool {
NSManagedObject *media = [mediaItems objectAtIndex:i];
[[self managedObjectContext] refreshObject:media mergeChanges:NO];
NSLog(#"i: %i", i);
i++;
}
}
This also didn't work, and crashed at the same point.
Did you try something like:
for(int i=0;i<[mediaItems count]; i++) {
#autoreleasepool {
NSManagedObject *media = [mediaItems objectAtIndex:i];
[media valueForKey:#"originalImage"];
}
}
Maybe this way "media" gets released properly at every loop.
(Sorry, I put this on a comment. I'm new on SO and not enough rep.)

NSOperation(s) leaks only on iOS 3 device

I have some NSOperations subclasses that handle CoreData imports. I believe i've ticked most of the non-main thread issues
I create my own autorelease pool in the
main method
I create a NSManagedObjectContext for each
operation
These operations are loaded into a NSOperationQueue, with the maximum number of concurrent operations set 1.
The code works perfectly on a iOS 4.0.1, however on a iOS 3.1.3 device a get a lot of log messages like the following
*** _NSAutoreleaseNoPool(): Object 0x5f926c0 of class NSCFDictionary autoreleased with no pool in place - just leaking
NSOperation Subclass main method
-(void) main{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#try {
//Start Operation
//====================================
NSManagedObjectID *objID = nil;
NSError *err = nil;
id user = nil;
if( !(userID = [self __lookup:[self userID] inContext: [self threadContext]]) ){
//Set the name of the element
user = [[self threadContext] objectWithID:objID];
//Update the name
[user setValue:#"John Doe" forKey:#"name"];
[user setValue:#"Hello world" forKey:#"status"];
}
if( ![[self threadContext] save:&err] ){
DebugLog(#"Couldn't savechanges %#", err);
}
//====================================
//End Operation
}
#catch (NSException * e) {
DebugLog(#"Exception %#",e);
}
//====================================
[pool drain];
}
The __lookup:inContext: method
-(NSManagedObjectID*) __lookup:(id)aID inContext:(NSManagedObjectContext*) aContext{
NSPredicate *predicate = nil;
NSEntityDescription *entity;
NSFetchRequest *fetchRequest = nil;
NSError *err = nil;
predicate = [NSPredicate predicateWithFormat:#"userID == %#",aID];
entity = [NSEntityDescription entityForName:#"User" inManagedObjectContext:aContext];
fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setPredicate:predicate];
[fetchRequest setEntity:entity];
//Only fetch id's for speed
[fetchRequest setResultType:NSManagedObjectIDResultType];
return [[aContext executeFetchRequest:fetchRequest error:&err] lastObject];
}
Most of the other methods instance methods, ie threadContext look similar to the __lookup:inContext: method. I'm aware that i don't create Autorelease pools for the instance methods, but according to my understanding of how autorelease works, as long as these methods are only called inside the main method, after the NSAutoreleasePool has been created, the outer most pool should be used. I create objects such as the NSManagedObjectContext lazily, and in most cases don't use the start method
Solved it, This operation was launching a NSURLConnection using the advice in an article by Dave Dribin. However, where possible i try to never cut and paste other peoples code, so i can always filter what i am putting into my own code.
Turns out i forgot to add this check
if( ![NSThread isMainThread] ){
[self performSelectorOnMainThread:#selector(start)
withObject:nil
waitUntilDone:NO];
return;
}
Which ensures that the start method runs on the main thread, where a NSAutoreleasePool already exists. Simple mistake, easy solution.

Weird crash when saving managedObjectContext

I am getting a weird crash when I try to save my model. This is my code:
TJModel *model = [TJModel sharedTJModel];
NSFetchRequest *request = [[[NSFetchRequest alloc] init]autorelease];
[request setReturnsObjectsAsFaults:NO];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TJVideoList"inManagedObjectContext:[model managedObjectContext]];
[request setEntity:entity];
NSError *error = nil;
NSMutableArray *mutableFetchResults = [[[model managedObjectContext] executeFetchRequest:request error:&error] mutableCopy];
if (error != nil)
NSLog(#"error %#",[error localizedDescription]);
TJVideoList *videoList = nil;
if ([mutableFetchResults count] == 0) {
videoList = (VideoList *)[NSEntityDescription insertNewObjectForEntityForName:#"TJVideoList"
inManagedObjectContext:[model managedObjectContext]];
}
else
{
videoList = [mutableFetchResults objectAtIndex:0];
}
[videoList addVideoListObject:recordedVideo];
error = nil;
if (![[model managedObjectContext] save:&error]) {
And crash .....This is what said in the terminal:
-[NSConcreteValue UTF8String]: unrecognized selector sent to instance 0x1d33f0
I thought it might be a cuestion of deallocated objects, so I retained them like this:
[managedObjectContext setRetainsRegisteredObjects:YES];
With no luck.
Your crash is not a result of this code.
Crashes in saves usually result from an error with an managedObject's attributes. In this case, you have somewhere assigned the wrong value to a string attribute. When the context goes to convert the string attribute to a UTF8 string for persistence, the object that is there instead of the NSString does not understand the message and the crash results.
Although this code should run okay, you do have some risky practices:
NSFetchRequest *request = [[[NSFetchRequest alloc] init]autorelease];
This is a bad practice. autorelease is the same as release. You should not send it to an object until you are complete done with it. autorelease marks an object for death the next time the memory pool is drained. In some case, that will kill the object unexpectedly. While it won't cause problems here, you don't want to get into the habit of taking this short cut because it will eventually bite you.
You should only use autorelease when the current scope is done with the object but the object is being sent outside the scope (usually in a method return.)
NSMutableArray *mutableFetchResults = [[[model managedObjectContext] executeFetchRequest:request error:&error] mutableCopy];
The mutable array here is pointless as is the copy. This is apparently in some reference material somewhere because it keeps cropping up novices code in the last few months. If you're not going to alter an array there is no reason to have it mutable. In the case of an array of managed objects, it is pointless to copy the array.
videoList = [mutableFetchResults objectAtIndex:0]
Since you have no sort descriptor for the fetch, the mutableFetchResults array will be in a random order. If you have more than one object returned, which is almost always the case, you will get a random TJVideoList object at the zero element every time you run the code.
Sounds more like you assigned an NSValue instance (NSNumber, most likely, as it is the most commonly used subclass) where an NSString was expected. The -retainsRegisteredObjects: is unlikely to be needed (it won't fix a memory related problem anyway).
It might possibly be an over-release issue, too. Try running with Zombie detection enabled (see the Run menu).

Core Data Memory Leak - iPhone iOS4

I desperately need help with a memory leak in my iPhone app. The app is ready to submit to the app store, is stable, has no memory leaks at all in iPhone simulator or Clang ... but seems riddled with them on my iPod Touch.
They all seem to stem from managedObjectModel when I'm trying to retrieve data from Core Data.
The Core Data code in my app was automatically created by Xcode a while back, I've noticed that the code has since changed when you get xcode to generate it ... I've tried with the old and new but it makes no difference.
If I comment out the following code, the problem goes away ... can anyway see what's wrong with it? I've spent 9 hours on this so far and just can't figure it out!
NSString *entityForName = [[NSString alloc] initWithString:#"OfflineSettings"];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityForName inManagedObjectContext:[self managedObjectContext]];
[request setEntity:entity];
[entityForName release];
NSSortDescriptor *sortById = [[NSSortDescriptor alloc] initWithKey:#"Id" ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sortById]];
[sortById release];
NSError *error;
NSMutableArray *mutableFetchResults = [[[self managedObjectContext] executeFetchRequest:request error:&error] mutableCopy];
if (mutableFetchResults == nil) {
// Handle the error.
NSLog(#"Error fetching");
}
int intId = -1;
if ([mutableFetchResults count] == 0) {
TTDERROR(#"No id has been saved to offline settings");
} else {
OfflineSettings *offlineSettings = (OfflineSettings *)[mutableFetchResults objectAtIndex:0];
intId = [offlineSettings.Id intValue];
}
[mutableFetchResults release];
[request release];
The leak specifically seems to be on this line:
NSMutableArray *mutableFetchResults = [[[self managedObjectContext] executeFetchRequest:request error:&error] mutableCopy];
.. and the code for [self managedObjectContext] is as follows in case it helps ..
- (NSManagedObjectContext *)managedObjectContext {
if (managedObjectContext_ != nil) {
return managedObjectContext_;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext_ = [[NSManagedObjectContext alloc] init];
[managedObjectContext_ setPersistentStoreCoordinator:coordinator];
}
return managedObjectContext_;
}
I'm really at a loss, so I would be so grateful for some help!
Steven
You don't need the mutable copy. executeFetchRequest: returns an autoreleased static array and you're not mutating the array. (I keep seeing this. Must be in an example somewhere.) Likewise, creating the entityForName NSString is pointless. Just put the string literal in the entityForName: to eliminate another possible source of error.
Niether of these are the likely source of the leak but you should remove them anyway.
As a rule of thumb, if you have troubles on device but not simulator or on one hardware but not others, then the problem is in a library/framework that is not properly compiled for the hardware where the error occurs. There really isn't any type of coder error that leaks in one environment but not others. When we make a mistake, it's universal.
It's also possible for resources such as images and sounds to behave differently because different devices use different graphics and audio hardware. That, however, is rather rare.
If you run the code through Instruments it should tell you exactly what object is leaking.

Calculating sum for decimal values via Core Data not working properly?

first time I post to this round, so please bear with me if I don't follow all the rules properly.
I am writing an app for the iPhone (OS 3.1) and am trying to write some code which lets me add decimals. I have a Core Data entity called SimpleItem with a amount attribute. Here is the test case I wrote:
// Create and configure objects
SimpleItem *si1 = [self createSimpleItem:#"si1"];
si1.amount = [NSDecimalNumber decimalNumberWithMantissa:1000 exponent:0 isNegative:NO];
SimpleItem *si2 = [self createSimpleItem:#"s12"];
si2.amount = [NSDecimalNumber decimalNumberWithMantissa:2000 exponent:0 isNegative:NO];
// Need to save prior to fetching results with NSDictionaryResultType (known limitation)
[self save];
// Describe fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"SimpleItem" inManagedObjectContext:self.context];
[request setEntity:entityDescription];
[request setResultType:NSDictionaryResultType];
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"amount"];
// For whatever reason, evaluating this expression here is absolutely not working. Probably decimals aren't handled properly.
NSExpression *sumAmountExpression = [NSExpression
expressionForFunction:#"max:"
arguments:[NSArray arrayWithObject:keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"amount"];
[expressionDescription setExpression:sumAmountExpression];
[expressionDescription setExpressionResultType:NSDecimalAttributeType];
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
// Fetch the amounts
NSError *error = nil;
NSArray *array = [self.context executeFetchRequest:request error:&error];
If I execute this code through otest and debug it, I get an exception when the fetch request is executed: "-[NSDecimalNumber count]: unrecognized selector sent to instance."
Just evaluating the keyPathExpression without the aggregate function works fine, though.
The reference documentation shows exactly the same example so I'm wondering what I am doing wrong. Or could this be just a bug?
All the best,
Harald
Copying your given source to a new iPhone project and running that in the simulator worked fine here. I was compiling against the 3.1.2 SDK on Snow Leopard.
This is what I used for the data model:
model description http://homepage.mac.com/aclark78/.Public/Pictures/test%20model.png
There must be something else going on causing your issue. Can you describe your model or simplify it further?
I think I had this problem before. IIRC, the problem is that you've set your fetch result type to NSDictionaryResultType but you're receiving it in an NSArray. Try switching the fetch result type to NSManagedObjectResultType which does return an array. All the examples in the docs use the default NSManagedObjectResultType.
In any case, the error message clearly results from an NSDecimal object being sent an array's count message. You could confirm this by trapping the result in an id. eg:
id *genericObj = [self.context executeFetchRequest:request error:&error];
NSLog("returned object=%#",genericObj);
NSLog("returned object class=%#",[genericObj class]);
Thanks for letting me know. The thing is that I actually don't want to use managed objects. After all, I am interested in the amounts only and this is what I get with the dictionary.
Also, trying to investigate the issue as you described didn't work as executing the fetch request immediately fails and I don't get anything back.
Any other ideas? Is there any other possibility to get aggregate values? Any "best practices"? At the moment I do the following (continuation of the code above):
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"amount"];
[expressionDescription setExpression:keyPathExpression];
[expressionDescription setExpressionResultType:NSDecimalAttributeType];
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
// Fetch the amounts
NSError *error = nil;
NSArray *array = [self.context executeFetchRequest:request error:&error];
if (array == nil) {
STFail(#"Fetch request could not be completed: %#", [error localizedDescription]);
}
STAssertEquals([array count], (NSUInteger) 2, #"Number of fetched objects != 2");
// Manually calculate the sum
NSDecimalNumber *sum = nil;
for (NSDictionary * dictionary in array) {
NSDecimalNumber *amount = (NSDecimalNumber *) [dictionary valueForKey:#"amount"];
if (!sum) {
sum = amount;
} else {
sum = [sum decimalNumberByAdding:amount];
}
}
STAssertNotNil(sum, #"Sum is nil");
STAssertTrue([sum isEqualToNumber:[NSDecimalNumber decimalNumberWithMantissa:3000 exponent:0 isNegative:NO]],
#"Sum != 3000: %#", sum);
Regards,
Harald