Plugging a sortedArrayUsingSelector and/or initWithArray Memory Leak Between Classes
I've been struggling to solve this memory leak for awhile now, so I'm hoping the community can provide some help. Memory Management is still an issue I'm working to understand (and yes I have the memory management guide).
According to the Instruments Leak tool, I'm leaking an NSArray as soon as I navigate backward (back button w/ Nav Controller) off of the pertinent screen. I'm show all the relevant code I can think of, below, and can share more if needed.
I know that I'm alloc/initing an array in the ordered array function. This is because, to the best of my understanding, sortedArrayUsingSelector
returns only pointers to the old array, not a true copy, so if I want to keep the array, I need to copy the values.
The problem is then how do I pass this sorted array to a different class while still properly managing my ownership of it? I release it in the dealloc, and I release it if the function is going to assign a new value, etc. But tbh I don't know if I'm doing this correctly.
Like I said, I'm really struggling still to properly understand how to correctly juggle all the memory management stuff, so any help would be much appreciated.
.h file of the relevant model class
@interface InstalledDataTracker : NSObject {
...other code...
NSArray *orderedZonesArray;
...other code...
}
@property (nonatomic, retain) NSArray *orderedZonesArray;
.m file of the relevant model class
@synthesize orderedZonesArray;
...other code...
- (NSArray *)orderedZonesArray {
if (!orderedZonesArray || installedDataChangedSinceLastRead) {
if (orderedZonesArray) {
[orderedZonesArray release];
}
NSArray *unorderedZones = [NSArray arrayWithArray:[self.installedAreas allKeys]];
orderedZonesArray = [[NSArray alloc] initWithArray:[unorderedZones sortedArrayUsingSelector:@selector(localizedCompare:)]];
}
return orderedZonesArray;
}
- (void) dealloc {
...other code...
[orderedZonesArray release], orderedZonesArray = nil;
[super dealloc];
}
.h in View Controller
#import <UIKit/UIKit.h>
@class InstalledDataTracker;
@interface SBVC_LSC01_ZoneSelect : UIViewController <UITableViewDataSource, UITableViewDelegate> {
... other stuff...
InstalledDataTracker *_dataTracker;
}
@property (nonatomic, retain) InstalledDataTracker *dataTracker;
.m init in View Co开发者_如何学Pythonntroller
@synthesize dataTracker = _dataTracker;
- (id)initWithPerson:(NSString *)person {
if (self = [super init]) {
...other stuff...
self.dataTracker = [[InstalledDataTracker alloc] init];
}
return self;
}
- (void)dealloc
{
...other stuff...
[self.dataTracker release];
[super dealloc];
}
Leaking Method in View Controller
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
AbbreviationLookup *lookup = [[AbbreviationLookup alloc] init];
NSString *abbreviatedZone = [self.dataTracker.orderedZonesArray objectAtIndex:[indexPath section]];
cell.textLabel.text = [lookup zoneForAbbreviation:abbreviatedZone];
[lookup release];
return cell;
}
Instruments Leak Trace:
0 libSystem.B.dylib calloc
1 libobjc.A.dylib class_createInstance
2 CoreFoundation __CFAllocateObject2
3 CoreFoundation +[__NSArrayI __new::]
4 CoreFoundation -[NSArray initWithArray:range:copyItems:]
5 CoreFoundation -[NSArray initWithArray:]
6 -[InstalledDataTracker orderedZonesArray]
7 -[SBVC_LSC01_ZoneSelect tableView:cellForRowAtIndexPath:]
Things I've tried
orderedZonesArray = [[[NSArray alloc] initWithArray:[unorderedZones sortedArrayUsingSelector:@selector(localizedCompare:)]] autorelease];
return [orderedZonesArray autorelease];
And a bunch of other stuff I can't remember. Many attempts I've made to properly "release" the ownership created by alloc/init result in some sort of crash/bad access in the view controller. This is contributing to my confusion over where to properly release the array...
Detailed replies very welcome. I still have a great deal to learn!
Thanks a bunch. (Also, I've had to change some class and methods names for project security, so if something doesn't seem to match please mention it and I'll recheck for a typo)
Edit:
@Daniel Hicks, when I remove the initWithArray
copy of the sorted array, as follows:
orderedZonesArray = [unorderedZones sortedArrayUsingSelector:@selector(localizedCompare:)];
, I get an EXC_BAD_ACCESS crash when the class tries to access the array from within the View Controller didSelectRowAtIndexPath
method (likely the next time the array is accessed, I believe). Here's the method. It crashes on the second NSLog line, so I've left that in for good measure:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(@"indexPath = %@", indexPath);
NSLog(@"self.dataTracker.orderedZonesArray = %@", self.dataTracker.orderedZonesArray);
NSString *abbreviatedZone = [self.dataTracker.orderedZonesArray objectAtIndex:[indexPath section]];
SBVC_LSC02_ZoneSelect *slz2 = [[SBVC_LSC02_ZoneSelect alloc] initWithPerson:self.selectedPerson andZone:abbreviatedZone];
[self.navigationController pushViewController:slz2 animated:YES];
[slz2 release];
}
In your viewController code, you allocate an InstalledDataTracker
object, and then pass it to a retain property. This results in a retain count of 2, not 1. Later, when you release the dataTracker object, you only reduce the retain count by one.
The easiest way to fix it would be to remove the self.
prefix so that you are not invoking the automatic retain
that is performed by the property accessor. In fact, I would recommend not using the dot syntax at all in your init
and dealloc
methods. There is some debate on the matter, but in general I think it is best to avoid calling your property accessors unless you have a very good reason to do so.
Here is how I would write it:
- (id)initWithPerson:(NSString *)person {
if (self = [super init]) {
...other stuff...
dataTracker = [[InstalledDataTracker alloc] init];
}
return self;
}
- (void)dealloc {
...other stuff...
[dataTracker release];
[super dealloc];
}
This is because, to the best of my understanding, sortedArrayUsingSelector returns only pointers to the old array, not a true copy, so if I want to keep the array, I need to copy the values.
This is a misinterpretation. sortedArrayUsing...
, similar other such functions, returns an array which contains the same pointer VALUES as in the original array. And, as the sorted array copy was made, the reference counts of the OBJECTS pointed to were incremented (ie, retain
was done on each copied pointer). So the sorted array and the original are both "equals", and neither "owns" the objects more than the other one does. (In fact, examining the innards of the two arrays you'd not be able to tell which was copied from which, other than if you noticed that one is sorted and the other isn't.)
So there's absolutely no need to make the additional copy of the sorted array.
When you make that additional copy, you're using an [[alloc] init...]
operation which returns a retained array. You then return that array to your caller without doing autorelease
on it, meaning that it will leak if your caller does not explicitly release
it, and meaning that the Analyzer will complain about it (since you can only return a retained object from copy...
et al).
精彩评论