【问题标题】:Instruments Pointing to Leak in Framework -- Found Leak was Somewhere Else (Why?)指向框架泄漏的仪器——发现泄漏在其他地方(为什么?)
【发布时间】: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


【解决方案1】:

这不是 100% 的答案,因为我们不知道接下来会发生什么以及之前发生了什么。

我不确定,但有些事情你不必做:
首先:

/*2a*/    self.resultsGeoCode = [[NSDictionary alloc] initWithDictionary:[resultsGeoCodeString JSONValue] copyItems:YES];

而不是那 3 行。 如果你不需要函数resultsGeoCodeString的参数,那么你可以释放它。

其次:

self.geometry = [[NSDictionary alloc] initWithDictionary:geom copyItems:YES];
[self.geometry release];

为什么在分配了这个对象之后就立即释放它?完成此对象后释放它。同上一点。

[pool release]; 之前添加行[self.resultsGeoCode release]; self.resultsGeoCode = nil; 或更早。

我建议您使用 ARC 而不是手动保留计数。

【讨论】:

  • 感谢您的帮助。 (1) 你能否澄清你在第一个建议中的意思,“如果你不需要论点......”? `resultsGeoCodingString" 是一个 NSString,我正在从 JSON 转换为 NSDictionary。我的想法是返回的对象不是自动释放的,或者如果是,它会带走一些提取的对象。这就是我转向复制的原因字符串和深度复制字典。(2)我的理解是保留属性的setter会调用retain。所以这个对象被保留了2X,因为我也使用init来创建它。但这不应该导致泄漏。跨度>
  • 如果你在分配 resultsGeoCodingString 之前的某个地方,你应该释放它。如果你不这样做 - 保持原样。 self.geometry = [[NSDictionary alloc] initWithDictionary:geom copyItems:YES];据我了解内存管理,这里你保留self.geometry一次。
  • 我查看了您的建议,即问题可能源于参数 resultsGeoCodingString 并没有解决问题。我想我现在已经缩小了可能是 SBJSON 问题的范围,我将修改我原来的问题以反映我所做的事情。
  • @Jim 告诉我们,您如何获得您的resultsGeoCodingString。这可能有助于我们为您提供正确的答案。
猜你喜欢
  • 2011-11-02
  • 2012-01-23
  • 2012-07-06
  • 2011-07-11
  • 1970-01-01
  • 2012-06-20
  • 2011-10-12
  • 2011-10-14
相关资源
最近更新 更多