【发布时间】:2012-03-15 20:23:57
【问题描述】:
我越来越熟悉用于调试的 Instruments,而且它比最初看起来更容易使用。发布此问题是为了让我能够理解为什么它指向一行代码作为我的泄漏源,以及解决它的逻辑过程是什么。
答案可能会使尝试使用仪器的其他人受益。我还没有看到很多关于如何使用它的细节。 (有 Instruments 用户指南,这是一个好的开始,但仅此而已。)
在这种情况下,Instruments 指向从 SBJSON 框架返回 JSONValue(类型 NSDictionary)的行处的可重复泄漏。我尝试了各种方法来隔离问题(见下文),并且在每种情况下,Instruments 仍然指向返回 JSON 字典对象的行。
我尝试的另一件事是 iOS 5 提供的 NSJSONSerializer。仪器再次指向同一行。显然,Instruments 误导了我。 (为什么?我能做些什么来避免/改善这种情况?)
长话短说,问题不在于 Instruments 指向的位置,而在于包含该行的实例内。在这种情况下,有两个属性,其值来自反序列化的 JSON 字典,没有被释放。
我通过反复试验得出这个结论,注释掉代码并用文字字符串替换返回的值。 (对不起,如果我吓到你了,Stig!)
较早的更新
这是我的帖子的修订版。我相信我已经将问题缩小到 SBJSON 返回的字符串没有被自动释放的失败。我想了解一下我的消除过程是否有意义、我应该得出什么结论以及如何解决这个问题。
原来的问题在下面。我专注于这段代码:
NSString *resultsGeocodeLiteralString = @"{... the rest of the string ...}";
NSAutoreleasePool *aPool = [[NSAutoreleasePool alloc] init];
/*1*/ NSDictionary *dico = [resultsGeocodeLiteralString JSONValue]; // <-- Instruments still points here.
/*2a*/ self.resultsGeoCode = [[NSDictionary alloc] initWithDictionary:dico copyItems:YES];
/*2b*/ [self.resultsGeoCode release];
[aPool release];
这里我使用文字字符串resultsGeocodeLiteralString,它使用SBJSON JSONValue 方法转换为NSDictionary。我使用文字字符串进行了测试,以将问题与原始输入参数及其内存管理分开。
暂时忽略自动释放池,我尝试了这段代码,将 JSONValue 结果深度复制到属性self.resultsGeoCode。
仪器指出泄漏发生在调用 JSONValue 方法的行。
这让我觉得自动发布有问题。所以我将这段代码包装在一个简短的自动释放池中。这对结果没有影响。仪器显示相同的泄漏,指向同一条线。
下面的原始问题:
Instruments 指出此代码是几次泄漏的来源。我已经没有关于如何解决这个问题的想法了。此方法位于Geocoder 类型的对象中
首先,这是代码。 Instruments 指向标有 /1/ 的线。我尝试深度复制作为 JSONValue(/2a/ 和 /2b/)返回的 NSDictionary,只是为了将问题与返回的对象分开,但这并没有有什么不同。
- (NSString *) processResults:(NSString *) resultsGeoCodeString {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
/*1*/ self.resultsGeoCode = [resultsGeoCodeString JSONValue];
/*2a*/ self.resultsGeoCode = [[NSDictionary alloc] initWithDictionary:self.resultsGeoCode copyItems:YES];
/*2b*/ [self.resultsGeoCode release]; // It's retained twwice in the line above.
/* copy a stirng */
self.previousStatusCode = [self.resultsGeoCode objectForKey:@"status"];
if ([self.previousStatusCode isEqualToString:@"OK"] == YES) {
/* Break it down. Results might be a single object or an array of objects. If it's an array, just take the first one. */
NSArray *address_components;
NSDictionary *geom;
if ([[self.resultsGeoCode objectForKey:@"results"] isKindOfClass:[NSArray class]]) {
address_components = [[(NSArray *)[self.resultsGeoCode objectForKey:@"results"] objectAtIndex:0] objectForKey:@"address_components"];
geom = [[[self.resultsGeoCode objectForKey:@"results"] objectAtIndex:0] objectForKey:@"geometry"];
}
else {
address_components = [[[self.resultsGeoCode objectForKey:@"results"] objectAtIndex:0] objectForKey:@"address_components"];
geom = [[self.resultsGeoCode objectForKey:@"results"] objectForKey:@"geometry"];
}
/* deep copy the geometry */
self.geometry = [[NSDictionary alloc] initWithDictionary:geom copyItems:YES];
[self.geometry release]; // it is retained twice in the line above
/* copy a string */
self.formattedAddress = [[[self.resultsGeoCode objectForKey:@"results"] objectAtIndex:0] objectForKey:@"formatted_address"];
/* copy the strings from specific types */
NSArray *typesArray;
for (NSDictionary *address_component in address_components) {
if ([[address_component objectForKey:@"types"] isKindOfClass:[NSArray class]])
typesArray = [address_component objectForKey:@"types"];
else
typesArray = [NSArray arrayWithObjects:[address_component objectForKey:@"types"], nil];
for (NSString *componentType in typesArray) {
if ([componentType isEqualToString:@"locality"])
self.city = [address_component objectForKey:@"long_name"];
else if ([componentType isEqualToString:@"administrative_area_level_1"])
self.region = [address_component objectForKey:@"long_name"];
else if ([componentType isEqualToString:@"country"]) {
self.country = [address_component objectForKey:@"long_name"];
self.countryCode = [address_component objectForKey:@"short_name"];
}
else if ([componentType isEqualToString:@"postal_code"])
self.postalCode = [address_component objectForKey:@"long_name"];
else if ([componentType isEqualToString:@"street_number"])
self.streetNumber = [address_component objectForKey:@"long_name"];
else if ([componentType isEqualToString:@"route"])
self.route = [address_component objectForKey:@"long_name"];
}
}
}
[pool release];
/* a retained property set to nil -- not needed anymore */
self.resultsGeoCode = nil;
/* return a string */
return self.previousStatusCode;
}
所有 NSString 属性都有复制属性。所有 NSDictionary 属性都有一个 retain 属性。你可以看到我对任何字典项做了一个深拷贝。
这里是dealloc方法:
- (void) dealloc {
/* NSString properties with copy attribute */
self.streetNumber = nil;
self.route = nil;
self.city = nil;
self.region = nil;
self.country = nil;
self.postalCode = nil;
self.previousStatusCode = nil;
self.region = nil;
/* I have tried it with and without these releases of the dictionary properties */
if (location_) [location_ release];
if (geometry_) [geometry_ release];
if (regionGeometries_) [regionGeometries_ release];
if (resultsGeoCode_) [resultsGeoCode_ release];
/* I would have thought these would be sufficient to release the retained properties */
// self.location = nil;
// self.geometry = nil;
// self.regionGeometries = nil;
// self.resultsGeoCode = nil;
[super dealloc];
}
Instruments 说泄漏对象是一个 NSCFString 并且负责的框架(我不知道那是什么)是 -[NSPlaceholderString initWithBytes:length:encoding:]。
我希望这里没有太多代码可以浏览,但我很难过。另外,我很好奇对于属性设置器的行为是否存在任何明显的误解。 (所有的 setter 和 getter 都是合成的。)
【问题讨论】:
-
一旦我理解了这个问题,我就可以把它抽象出来,我很乐意这样做。我认为我已经很好地缩小了这个范围,并且我试图在这里了解一些原则。一旦我理解了它们,它们就会被分享。
-
吉姆,您使用的是哪个版本的 SBJson?当前的主 (3.1alpha) 正在使用 ARC。这应该排除任何泄漏。你能看看你是否仍然看到这个版本的问题?
-
嗨,斯蒂格。我正在使用 3.0.1。我刚刚解决了这个问题,你会很高兴知道它不是 SBJSON!我将修改我的问题以反映这一点,并说明问题所在。我尝试了 NSJSONSerializer 并发生了同样的问题。 Instruments 仍然指向 JSON 序列化代码行。 (我切换回 SBJSON 因为我自定义了 m 个对象的序列化。)
标签: ios memory-management memory-leaks instruments