I am checking Day objects in my schedule Dictionary and want to know what to do about the Day pointer when I'm done. Build and Analyze doesn't complain about it, but it just sits there taunting me. I feel like it's just sitting there when the function finishes and maybe even a new one gets created each time through the loop. Also, when releasing day each time through the loop I end up releasing the original object. Any ideas?
- (NSUInteger) showsInTheNext:(NSUInteger)days {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyyLLdd"];
if(days == 0) days = 91;
NSUInteger shows = 0;
for (NSUInteger x = 0; x < days; x++)
{
Day *day = [self.schedule objectForKey:[dateFormatter stringFromDate:[self.date addTimeInterval:60*60*24*x]]];
if((day != nil) && ([day.type isEqualToString:#"Show"])) shows++;
//[day release];
}
[dateFormatter release];
return shows;
}
The NSDictionary retains the NSObject subclass when it is added to the NSDictionary. When you fetch it back out no additional retain message is sent, so you should discard the pointer without sending a release message.
If the object is removed from the dictionary, the NSDictionary implementation will send a release message to the object being removed.
You can verify this by doing debug-level checks of the retainCount to make sure it doesn't change.
You are simply pulling an instance of Day that already exists from your schedule dictionary. You don't need to (and shouldn't) release it when you are done unless you are removing it from the dictionary. Nowhere in your loop is a new instance of Day being created.
Related
I tried something out in my code to see the effect on memory utilization. I wanted to find out if the line inside the loop was leaking. Running this loop took utilization up to 100MB and it didn't go back down again. Does this indicate a memory leak? If so why? (I'm using ARC)
for (i = 0; i < 10000000; i++)
{
self.accounts = [[NSArray alloc] initWithArray:[_dal accounts] copyItems:YES];
}
(accounts is an array of AccountSummary objects which implements NSCopying like this: name city state phone are all NSStrings, isLocal is BOOL)
- (id)copyWithZone:(NSZone *)zone {
AccountSummary *newAccount = [[AccountSummary allocWithZone:zone] init];
newAccount.name = [self.name copyWithZone:zone];
newAccount.city = [self.city copyWithZone:zone];
newAccount.state = [self.state copyWithZone:zone];
newAccount.phone = [self.phone copyWithZone:zone];
newAccount.isLocal = self.isLocal;
return newAccount;
}
There's no leak here that I can see. There will, however, be quite a bit of peak memory usage.
The exact behaviour of things that should logically release memory varies. Quite often, instead of releasing the memory it's autorelased. (With MRR, there used to be a method called autorelease.) When you autorelease something, it isn't really released but is instead scheduled for release later, when your code is finished because it's returned to the main event loop.
If part of this is being autoreleased — and my guess is that the property assignment is autoreleasing, because autorelease is "safer" than hard releasing — that memory won't be deallocated until your next autoreleasepool flush. Code on the main thread has an autoreleasepool set up by the OS itself, so each time you return to the main event loop everything that's been autoreleased gets flushed out. Here, that probably means that all 10,000,000 copies are kept in memory until you return to the main event loop. Darn right that'll crash a real device. :)
(That's assuming you're on the main thread; if you're not, you may not even have an autorelasepool set up, which means you probably will get a leak. But I think you get warnings to console in this case, so you'd already have a hint about which way to go.)
You can reduce this peak memory usage by using #autoreleasepool:
for (i = 0; i < 10000000; i++) #autoreleasepool {
self.accounts = [[NSArray alloc] initWithArray:[_dal accounts] copyItems:YES];
}
What will happen now is that the memory scheduled for release later in each iteration of the loop will actually be released each iteration of the loop. That should solve your immediate problem.
That said, i can't imagine why you're doing this except to check the behaviour. And if that's the case, this is unlikely your core problem.
Assuming your core problem is a leak, with ARC you're not really looking for leaks. You're looking for circular references. That means your answer likely lies elsewhere in your code. If you're sure it's self's accounts rather than dsl's accounts that are leaking, look for self being involved in a circular loop.
Also, keep in mind that calling copyWithZone: on a NSString will probably not copy the string. (There's no need to copy a read-only string, as the read-only string can't be changed. Both "copies" can be the same object.) So if you're leaking just strings, they could be associated with the original objects.
When creating lots of objects inside a loop, you should do that inside an auto release pool.
#autoreleasepool {
for (i = 0; i < 10000000; i++) {
self.accounts = [[NSArray alloc] initWithArray:[_dal accounts] copyItems:YES];
}
}
or, more likely in the real world...
for (i = 0; i < 10000000; i++) {
#autoreleasepool {
self.accounts = [[NSArray alloc] initWithArray:[_dal accounts] copyItems:YES];
}
}
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Objective C Memory Management
My code is showing a memory leak here:
NSMutableArray* newImageArray = [[NSMutableArray alloc] init];
NSMutableArray* newMediaArray = [[NSMutableArray alloc] init];
if (self.categoryIndex == 0) {
for (int i=1; i < [categoryArray count]; i++)
{
newImageArray = [NSMutableArray arrayWithArray:[newImageArray arrayByAddingObjectsFromArray:[self getImageArrayByCategoryIndex:i]]];
}
}
else {
newImageArray = [self getImageArrayByCategoryIndex:self.categoryIndex];
}
for (int i=0; i < [newImageArray count]; i++)
{
Media* media = [[Media alloc] init];
NSString* imageFile = [newImageArray objectAtIndex: i];
media.imageFile = [UIImage imageNamed:imageFile];
media.imageLabel = [[imageFile lastPathComponent] stringByDeletingPathExtension];
media.soundFile = [appFolderPath stringByAppendingString:[[[imageFile stringByDeletingPathExtension] stringByAppendingString: #".wav"] stringByReplacingOccurrencesOfString: IMAGES_FOLDER withString: SOUNDS_FOLDER]];
[newMediaArray addObject:media];
}
self.mediaArray = newMediaArray;
[self setNextMediaIndex];
I am not releasing media because it is being used by newMediaArray (which is used by mediaArray, which is used my my main object). Shouldn't everything get released when I release my main object?
It looks like you are leaking all over the place in a variety of ways
newImageArray gets allocated but never released, additionaly you are overwriting the version that you allocated in the first line of you code with another version. So even if you released it at the end of this code segment, the wrong version would get released. It looks like you don't even need to allocate this one.
newMediaArray gets allocated but never released, you assign it to a property mediaArray if you are using #synthesize to create the code for that property, depending on how you declared that property, the setter will retain the value i.e. newMediaArray creating a leak.
media gets allocated but never released, it get added to a NSMutableArray which means it will get retained by the array. If your app crashes when you release media in the for loop the problem is somewhere else
The Memory Management Programming Guide is pretty much a must read
When an NSMutableArray such as newMediaArray adds an object, it will retain that object. You don't need to (nor should you) retain the object on the array's behalf. This is fundamentally how memory management in Objective-C works: each object retains the things it references, and releases them when finished. newMediaArray is its own object, so it'll manage its own references.
You should release media near the end of the body of your for loop because you're done using that object. If you don't release it then, you'll lose your reference to it and you'll have no way to release it in the future.
You do
[newMediaArray addObject:media];
that means that newMediaArray has done a retain on media. You can then release media (you should). The retain done in the array method will keep it alive as long as the array references it. If you don't release it in your method, the retain count will remain 2, and even if the array releases it, it will still be 1 and not be dealloc-ed.
You could do:
Media *media = [[[Media alloc] init] autorelease];
Then the autorelease pool will release it in time, but not before this method ends.
You need to have the [media release] statement at the bottom of the loop. newMediaArray should also be released after it is assigned to the mediArray property.
It seems like I keep asking the same questions, memory related. My current code works exactly as I intend it, but I cannot figure why I am showing a leak here in Instruments.
-(NSDate *)startTimeAndDate {
NSDate *dateToReturn = nil;
if (startTimeAndDate != nil) {
dateToReturn = [startTimeAndDate retain];
} else { //is currently nil, this will be the initial setting
//return default time if we have a working date
if (finishTimeAndDate != nil) {
dateToReturn = [[self dateFromDate:finishTimeAndDate withNewTime:defaultStartTime]retain];
} else {
//return the default time with today's date if we have nothing set as yet
dateToReturn = [[self dateFromDate:[NSDate date] withNewTime:defaultStartTime] retain];
}
//save the initial setting
self.initialStartDateAndTime = [[dateToReturn copy] autorelease];
}
[startTimeAndDate release];
startTimeAndDate = dateToReturn;
return startTimeAndDate;
}
-(void)setStartTimeAndDate:(NSDate *)inStartTimeAndDate {
BOOL initialAssignment = NO;
if (startTimeAndDate == nil) {
initialAssignment = YES;
}
if (startTimeAndDate != inStartTimeAndDate) { //skip everything if passed object is same as current
//check that the start time is prior to finish only if finish time has been entered
NSDate *dateToSetStartTo = nil;
if (finishTimeAndDate != nil) {
if ([inStartTimeAndDate earlierDate:finishTimeAndDate] == inStartTimeAndDate) {
// use the new time, it is earlier than current finish time
dateToSetStartTo = [inStartTimeAndDate retain];
} else { //start time is not earlier then finish time
// the received entry is invalid, set start time to 1 default interval from finish
dateToSetStartTo = [[finishTimeAndDate dateByAddingTimeInterval:-self.defaultTimeInterval] retain];
}
} else { //finish time is nil
// use the new time without testing, nothing else is set
dateToSetStartTo = [inStartTimeAndDate retain];
}
[startTimeAndDate release];
startTimeAndDate = dateToSetStartTo;
}
if (initialAssignment) {
self.initialStartDateAndTime = [[self.startTimeAndDate copy] autorelease];
}
}
So far as I can see, I am balancing all retains with release or autorelease. The leak appears to be caused on the first pass only. I have a view controller, it creates my model (wherein this code lies) and sets a start date, nothing else is done at that point. If I close that view controller at that point, Instruments shows that I am leaving the date object as a leak.
I placed a NSLog to show retain count at dealloc and, sure enough, it has retain count of 2 before my final release is called, leaving a retain count of 1 when it should have been destroyed. It is always the same regardless if I close immediately after initialization or set and get a hundred times. retainCount is 2 prior to my final call to release in dealloc.
I have been looking at this all weekend and cannot figure where I've gone wrong.
To clarify, the initial call is to set the startTimeAndDate property. At that point all other fields are nil or 0 if not objects. That startTimeAndDate object appears to be the leaking object.
Firstly, can you describe the problem you are trying to solve with this code? I ask because it appears very complex and my initial thought is that simplification will not only clarify what you are doing, but is also likely to solve your leak as well.
Secondly, (and I may have this wrong), you only need to retain/release objects if you expect those objects to exist beyond the scope of the method, or you expect that they may be released by some code that you are claling in you method. Based on this, you appear to be over retaining and releasing in your code. I think you can remove a lot of it.
Again I may be wrong, but it appears that you will indeed leak. The reason I think so is this - on your first pass you retain some data in dateToReturn which is a local variable. Then you do
self.initialStartDateAndTime = [[dateToReturn copy] autorelease];
But this is not releasing dateToReturn. Instead it is releasing the copy of dateToReturn. dateToReturn is still retained. Presuming that you intend to autorelease the copy because initialStartDateAndTime is set with retain, I think you should be doing:
self.initialStartDateAndTime = [[dateToReturn copy] autorelease];
[dateToReturn release];
Of course, if you remove the extra retain/release's then this becomes simpler again.
The final thing I would suggest is around naming. The problem with code like this is that you have a number of methods and variables, all with very similar names. This can make it difficult to follow and lead to bugs. So ask yourself if you really need this many variables. And can you make your code more readible by changing some of the names.
Dam, ignore what I said. I just went through the code again and you're right. I think you are basically being burned by the complexity of the code. I found it quite difficult to follow, especially with the number of properties. I think what I would do at this stage is to copy the code to a unit test and run it from there. Then you can better test and debug it. I would recommend GHUnit if you do not already have unit testing in place.
The other thing that occurs to be is that there is code executing somewhere else in your program that is retaining the date. Therefore triggering the leak. For example if inStartTimeAndDate is coming in with a retain count of 1, but is not released by the code that called the setter then you could end up with startTimeAndDate with a retain of 2.
Having said that, here's my rewrite of the getter in an attempt to clarify whats going on:
-(NSDate *)startTimeAndDate {
// If we have it, bail out fast.
if (startTimeAndDate == nil) {
return startTimeAndDate;
}
// Is currently nil, this will be the initial setting
NSDate *dateToReturn = nil;
//return default time if we have a working date
if (finishTimeAndDate != nil) {
dateToReturn = [self dateFromDate:finishTimeAndDate withNewTime:defaultStartTime];
} else {
//return the default time with today's date if we have nothing set as yet
dateToReturn = [self dateFromDate:[NSDate date] withNewTime:defaultStartTime];
}
//save the initial setting
self.initialStartDateAndTime = [[dateToReturn copy] autorelease];
startTimeAndDate = [dateToReturn retain];
return startTimeAndDate;
}
The main reason for this re-write was that it appeared that if there was a startTimeAndDate then the code was doing this:
dateToReturn = [startTimeAndDate retain];
...
[startTimeAndDate release];
startTimeAndDate = dateToReturn;
Which seemed a little pointless because it's effective doing a retain, release and self assignment. It would work, but there's less chance of a bug if we leave it out.
NSMutableArray *m_res = [NSMutableArray arrayWithCapacity:ticks];
double t = lo_t;
while (t <= hi_t) {
[m_res addObject:[NSDecimalNumber decimalNumberWithDecimal:
[[NSNumber numberWithDouble:t] decimalValue]
]];
t += delta_t;
}
return [[NSArray arrayWithArray:m_res] retain];
It is supposed to return a persistent NSArray containing some values. I plan to call release on it when it is no longer needed. Is it ok or there's some bug, because when I call function containing this code my program stops working (and it's a memory issue not endless loop).
The code is safe (ie won't crash) but the last line is incorrect.
The arrayWithArray is doing nothing useful and the retain is a leak.
It should be
return m_res;
If you hava a crash the cause is elsewhere.
I have a method (the code below is a simplified version) that parses small text files:
- (void)parseFile:(NSString *)aFile
{
NSDate *date;
NSNumber *number;
NSString *desc;
NSString *txt = [NSString stringWithContentsOfFile:aFile encoding:NSUTF8StringEncoding error:nil];
for (NSString *line in [txt componentsSeparatedByString:#"\n"]) {
if ([linesubstring isEqual:#"mydate"]) {
date = [dateFormat dateFromString:strDate];
}
if ([linesubstring isEqual:#"mynumber"]) {
number = [numberFormat numberFromString:strValue];
}
if ([linesubstring isEqual:#"mydesc"]) {
desc = [line substringWithRange:NSMakeRange(0, 10)];
}
if (!date && !number && !desc) {
...do something...
}
}
}
The first problem is that date variable is being filled with the content of aFile parameter. It only assumes it's correct value, when the passes through the fist if/check.
So why? I though that date could be a reserved word and exchanged it, but with the same behavior.
The second problem is with the last if (with the nested ones). Debuging the code, i can see that xcode shows it as "out of scope", but !number fails (xcode thinks that it's valid)...
I tried other combinations, like [number isNotEqualTo:[NSNull null]] (this one throws an error EXC_BAD_ACCESS), without success.
Please, could anybody give some hints? I'm newbie with cocoa/objective-c. I'm coming from java...
TIA,
Bob
There's quite a few things wrong with the code you've provided. I'm using the answer box because there isn't enough room for this to be a comment:
With regards to your variable declarations:
NSDate *date;
NSNumber *number;
NSString *desc;
You have correctly declared them, but you have not initialised them. As they are, they could be pointing to any random garbage. This means that your test at the end of the loop…
if (!date && !number && !desc) {
...do something...
}
…may in fact always execute because date, number and desc may always be non-zero (I say may because it is actually undefined whether they are zero or non-zero). Initialise each of them to nil if you plan to determine whether they are set or not:
NSDate *date = nil;
NSNumber *number = nil;
NSString *desc = nil;
It is not always necessary to initialise variables (for example, as long as you write to it before you read from it, it is not necessary to initialise it), however some people promote the idea of initialising all variables to prevent this undefined behaviour from surfacing (I typically initialise all variables even if I overwrite the initialised value anyway).
Also, there is a variable called linesubstring but it is not declared anywhere in the code, similarly strDate, strValue are not declared anywhere either. It is important to know how these are declared and how these are used as they may similarly be pointing to garbage.