堵塞类之间的sortedArrayUsingSelector和/或initWithArray内存泄漏
我已经努力解决这个内存泄漏有一段时间了,所以我希望社区能够提供一些帮助。内存管理仍然是我正在努力理解的一个问题(是的,我有内存管理指南)。
根据 Instruments Leak 工具,一旦我向后导航(带导航控制器的后退按钮)离开相关屏幕,我就会泄漏 NSArray。我在下面展示了我能想到的所有相关代码,如果需要的话可以分享更多。
我知道我正在有序数组函数中分配/初始化一个数组。这是因为,据我所知,sortedArrayUsingSelector 仅返回指向旧数组的指针,而不是真正的副本,因此如果我想保留数组,我需要复制值。
问题是如何将这个排序数组传递给不同的类,同时仍然正确管理我对它的所有权?我在 dealloc 中释放它,如果函数要分配一个新值等,我会释放它。但是说实话,我不知道我是否正确执行了此操作。
就像我说的,我仍然在努力正确理解如何正确处理所有内存管理内容,因此任何帮助将不胜感激。
相关模型类的.h 文件
@interface InstalledDataTracker : NSObject {
...other code...
NSArray *orderedZonesArray;
...other code...
}
@property (nonatomic, retain) NSArray *orderedZonesArray;
相关模型类的.m 文件
@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];
}
View Controller 中的.h
#import <UIKit/UIKit.h>
@class InstalledDataTracker;
@interface SBVC_LSC01_ZoneSelect : UIViewController <UITableViewDataSource, UITableViewDelegate> {
... other stuff...
InstalledDataTracker *_dataTracker;
}
@property (nonatomic, retain) InstalledDataTracker *dataTracker;
View 中的 init控制器
@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];
}
视图控制器中的泄漏方法
- (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;
}
仪器泄漏跟踪:
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:]
我尝试过的事情
orderedZonesArray = [[[NSArray alloc] initWithArray:[unorderedZones sortedArrayUsingSelector:@selector(localizedCompare:)]] autorelease];
return [orderedZonesArray autorelease];< /code>
还有一堆其他的东西我不记得了。我为正确“释放”由 alloc/init 创建的所有权所做的许多尝试导致视图控制器中出现某种崩溃/错误访问。这让我对在哪里正确释放数组感到困惑......
非常欢迎详细回复。我还有很多东西要学!
非常感谢。 (此外,为了项目安全,我必须更改一些类和方法名称,因此如果某些内容似乎不匹配,请提及它,我将重新检查是否有拼写错误)
编辑:
@Daniel Hicks,当我删除排序数组的 initWithArray
副本时,如下所示:
orderedZonesArray = [unorderedZones sortedArrayUsingSelector:@selector(localizedCompare:)];
当类尝试从视图控制器内访问数组时,我遇到 EXC_BAD_ACCESS 崩溃didSelectRowAtIndexPath
方法(我相信可能是下次访问数组时)。这是方法。它在第二个 NSLog 行上崩溃,所以我将其保留下来以备不时之需:
- (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];
}
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 Controller
@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];
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
在 viewController 代码中,分配一个 InstalledDataTracker 对象,然后将其传递给保留属性。这会导致保留计数为 2,而不是 1。稍后,当您释放 dataTracker 对象时,只会将保留计数减少 1。
修复此问题的最简单方法是删除
self.
前缀,以便您不会调用由属性执行的自动retain
访问器。事实上,我建议在init
和dealloc
方法中根本不要使用点语法。关于这个问题存在一些争论,但总的来说,我认为最好避免调用您的属性访问器,除非您有充分的理由这样做。我会这样写:
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 automaticretain
that is performed by the property accessor. In fact, I would recommend not using the dot syntax at all in yourinit
anddealloc
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:
这是一种误解。
sortedArrayUsing...
与其他此类函数类似,返回一个数组,其中包含与原始数组中相同的指针 VALUES。并且,当进行排序的数组复制时,所指向的 OBJECTS 的引用计数会增加(即,在每个复制的指针上进行retain
)。因此,排序后的数组和原始数组都是“相等”的,并且两个数组都比另一个数组“拥有”更多的对象。 (事实上,检查两个数组的内部结构,您无法判断哪个数组是从哪个数组复制的,除非您注意到一个已排序而另一个未排序。)因此绝对没有必要使已排序数组的附加副本。
当您制作额外的副本时,您将使用[[alloc] init...]
操作,该操作返回保留的数组。然后,您将该数组返回给调用者,而不对其执行autorelease
操作,这意味着如果您的调用者没有显式release
它,它将泄漏,并且意味着分析器会抱怨它(因为您只能从copy...
等返回保留的对象)。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 doingautorelease
on it, meaning that it will leak if your caller does not explicitlyrelease
it, and meaning that the Analyzer will complain about it (since you can only return a retained object fromcopy...
et al).