Allocating NSDateFormatter (or NSNumberFormatter) is relatively slow and
cellForRowAtIndexPath runs for every cell.
So, allocating formatters in cellForRowAtIndexPath can be a significant contributor to jerky scrolling.
To smooth scrolling, I tried allocating them outside cellForRowAtIndexPath,
by making them a class variable and allocating them in viewWillAppear and releasing them in viewWillDisappear (see code below).
But that produce leaks in the formatters.
Where is the best place to declare/allocate/release formatters used in cellForRowAtIndexPath?
//in myNavigationViewController.m:
NSDateFormatter *myDateFormatter;
-...viewWillAppear...{
if(myDateFormatter){ //Solution: add this check.
[myDateFormatter release];
}
myDateFormatter = [[NSDateFormatter alloc] init];
[myDateFormatter setDateStyle:NSDateFormatterShortStyle];
[myDateFormatter setTimeStyle:NSDateFormatterShortStyle];
if(locale){ //Solution: add this check.
[locale release];
}
locale = [NSLocale currentLocale];
[myDateFormatter setLocale:locale];
}
-...cellForRowAtIndexPath... {
cell.myDateLabel.text = [myDateFormatter stringFromDate:_date];
}
-...viewWillDisappear...{
// [myDateFormatter release]; //Solution: remove this line
}
-...dealloc {
[myDateFormatter release]; //Solution: add these 2 lines.
[locale release];
}
It shouldn't leak at all so long as you remember to deallocate it in -dealloc.
Related
I get leaks if I dont put it in dealloc. I get a crash EXC_BAD_ACCESS If I do. I cannot see anything wrong with this code. The bad access is pointed at [events release]. Have I made a mistake in the code below or is Instruments just having a laugh at my expense?
events is an NSArray
#interface EventsViewController : UITableViewController
{
#private
NSArray *events;
}
- (void)viewDidLoad
{
events = [[self getEvents] retain];
}
- (void)dealloc
{
[events release];
[super dealloc];
}
- (NSArray*)getEvents
{
NSMutableArray *response = [[[NSMutableArray alloc] init] autorelease];
//Some sql
while(sqlite3_step(statement) == SQLITE_ROW)
{
Event *event = [[[Event alloc] init] autorelease];
event.subject = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
[response addObject:event];
}
return response;
}
Update
A lot of you are saying the code is fine which is a plus. I dont manipulate events elsewhere - I have removed any code that does to try and single out the crash. Perhaps its in the parent view?
This is the click event that pushes the EventsViewController:
- (void)eventsClick:(id)sender
{
EventsViewController *eventsViewController = [[EventsViewController alloc] initWithNibName:#"EventsViewController" bundle:nil];
eventsViewController.anywhereConnection = anywhereConnection;
eventsViewController.contact = contact;
[[self navigationController] pushViewController:eventsViewController animated:YES];
[eventsViewController release];
}
The crash is actually happening when I return to the parent view. (I think it is considered a parent in this scenario). But perhaps the [eventsViewController release] just triggers dealloc in the EventViewController.
Have you considered just refactoring your code to use ARC? It works with iOS 4 and up and will make your life a lot easier. There are plenty of tutorials out there that will guide you how to do it, and will remove the need to manually figure out the nuances of memory management.
If your Events object has property 'subject' set as assign, then the results of stringWithUTF8String: will not be retained. (Same thing if Events is a C++ object.)
The stringWithUTF8String: method returns an auto-released object that will be released at the next turn of the event loop.
There is a huge difference when you reference a variable via "self", and when you don't.
When you use
events = [[self getEvents] retain];
the memory allocated in getEvents never gets stored in the class property and is basically a leak.
You need to use
self.events = [self getEvents]; // no need to use retain if property is correctly defined.
Then
[events release];
should work fine.
try putting
events = nil;
in dealloc.
I created a common method "isValidAmount" to check if the user inputs a correct amount in UITextField. This is working, but the problem is, I am getting leaks when the number is invalid. The leaked object is "NSCFNumber" and the responsible for that is "getObjectValue". I don't know what else to do. I already release the "formatter" below. I even tried to release "number" below. But still I keep on getting this leak. Please help.
+ (BOOL) isValidAmount:(NSString *)amount {
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber *number = [formatter numberFromString:amount];
[formatter release];
if (!number) {
return NO;
}
return YES;
}
By inspection, there is nothing wrong with that code.
Does it also leak on the device? Can you post the exact backtrace of the allocation that is leaked?
Can anyone see potential leaks in this code? I'm getting a "100%" leak according to Instruments on the line "NSString *ender = [temp stringFromDate:now];"
NSDateFormatter* temp = [[NSDateFormatter alloc] init];
[temp setDateFormat:#"yyyy-MM-dd"];
NSDate *now = [NSDate date];
NSString *ender = [temp stringFromDate:now];
DashboardViewController *controller = [[DashboardViewController alloc] init];
[controller initWithStartDate:ender andEndDate:ender];
[controller initAccount:account];
[self presentModalViewController:controller animated:NO];
[temp release];
Do you release controller after all that stuff?
This advice is unrelated to original question, but I think you should rename the initWithStartDate:andEndDate: and initAccount: methods since typically methods with "init" in the name return new instances.
Perhaps create your own -(id)initWithStartDate:endDate:account: and call the designated initializer from within.
Then you would create a new controller instance with
DashboardViewController *controller = [[DashboardViewController alloc] initWithStartDate:ender endDate:ender account:account];
Gonzalo
Since you pass your controller instance to the -presentModalViewController: method, that method will retain your controller. So you can safely release your controller, but you also should release your controller, since the memory management rules state that objects that you alloc+inited are owned by you and must be released.
On the other hand - just a small note - NSDateFormatter is a "heavy" object, cache the instance and reuse it, if it's possible. Probably this is also the reason why Apple deprecated this method. You might call -init on NSDateFormatter from iOS 2.0 till iOS 3.2, but it is deprecated after iOS 3.2 .
Instruments tells me the following line from the code below is leaking: I can't figure out how to fix this leak.
[self.selectedElement.usrAdvancedBuyingPercents replaceObjectAtIndex:selectedRow withObject:[numberFormatter stringFromNumber:percentage]];
- (IBAction) simpleMarginSliderValueChanged:(UISlider *)sender {
NSDecimalNumber *percentage = (NSDecimalNumber *)[NSDecimalNumber numberWithFloat:[sender value]];
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setPositiveFormat:#"#.##"];
[self.selectedElement.usrAdvancedBuyingPercents replaceObjectAtIndex:selectedRow withObject:[numberFormatter stringFromNumber:percentage]];
[numberFormatter release];
}
The NSString you are creating from the number is not being released somewhere.
The problem is not in the code that is shown - it's somewhere else that is taking a string from that array, retaining it, then not releasing it. Leaks just shows you where memory that is leaked was initially allocated, and the only thing on that line that is allocating memory is [numberFormatter stringFromNumber:percentage].
Either that, or the whole array is not being released correctly (but then whatever builds usrAdvancedBuyingPercents would also show that is leaking).
I have a weird memory leak with NSTimeIntervall and NSDate. Here is my code:
NSTimeInterval interval = 60*60*[[[Config alloc] getCacheLifetime] integerValue];
NSDate *maxCacheAge = [[NSDate alloc] initWithTimeIntervalSinceNow:-interval];
if ([date compare:maxCacheAge] == NSOrderedDescending) {
return YES;
} else {
return NO;
}
date is just an NSDate object, this should be fine. Instruments tells me that "interval" leaks, yet I do not quite understand this, how can I release a non-object? The function ends after the code snippet I posted here, so from my understanding interval should get automatically deallocated then.
Thanks a lot!
It is probably telling you that a leak is happening on that line.
The expression [[[Config alloc] getCacheLifetime] integerValue] is your problem.
First of all, you care creating an object (calling alloc) but you lose the reference to it before calling release or autorelease, so it is leaking.
Also, you really ought to call an init method immediately after allocating the object. Even if your Config class doesn't do anything special, NSObject's init method will need to be called.
If you replace that line with
Config *config = [[Config alloc] init];
NSTimeInterval interval = 60*60*[[config getCacheLifetime] integerValue];
[config release];
That leak should be plugged up.
You are also leaking the maxCacheAge object. Inserting [maxCacheAge autorelease]; before the if statement should fix that.
Found the problem, in case you come across the same issue, this is the solution:
[[ClubzoneConfig alloc] loadConfigFile];
NSTimeInterval interval = 60*60*[[[ClubzoneConfig alloc] getCacheLifetime] integerValue];
NSDate *maxCacheAge = [[NSDate alloc] initWithTimeIntervalSinceNow:-interval];
if ([date compare:maxCacheAge] == NSOrderedDescending) {
[maxCacheAge release];
return YES;
} else {
[maxCacheAge release];
return NO;
}
The problem is that the maxCacheAge object needs to get released, as I own it (see link below).
I got it thanks to the awesome solution here: iPhone Memory Management