ios - 指向框架中泄漏的仪器——发现泄漏在其他地方(为什么?)

标签 ios memory-management memory-leaks instruments

我对调试工具越来越熟悉,它比最初看起来更容易使用。发布这个问题是为了让我能够理解为什么它指向一行代码作为我的泄漏源,以及解决它的逻辑过程是什么。

这个答案可能会让其他尝试使用仪器的人受益。我还没有看到很多关于如何使用它的细节。 (有《仪器用户指南》,这是一个好的开始,但仅此而已。)

在这种情况下,Instruments 指出从 SBJSON 框架返回 JSONValue(类型 NSDictionary)的行存在可重复泄漏。我尝试了各种方法来隔离问题(见下文),并且在每种情况下,Instruments 仍然指向返回 JSON 字典对象的行。

我尝试的另一件事是 iOS 5 中提供的 NSJSONSerializer。Instruments 再次指向同一行。显然,Instruments 误导了我。 (为什么?我可以做什么来避免/改善这种情况?)

长话短说,问题不在于 Instruments 所指向的位置,而在于包含该行的实例内。在本例中,有两个属性(其值源自反序列化的 JSON 字典)未释放。

我通过反复试验、注释掉代码并用文字字符串替换返回值得出了这个结论。 (斯蒂格,如果我吓到你了,我很抱歉!)


较早的更新

这是我的帖子的修订版。我相信我已经将问题范围缩小到 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

类型的对象中

首先,这是代码。仪器指向标记为/1/的线。我尝试深度复制作为 JSONValue 返回的 NSDictionary(/2a/和/2b/),只是为了将问题与返回的对象区分开来,但这并没有使有什么区别。

- (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 属性都有一个保留属性。您可以看到我对所有字典项目进行了深层复制。

这是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 都是合成的。)

最佳答案

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

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

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

而不是那三行。 如果您不需要函数 resultsGeoCodeString 的参数,则可以将其释放。

其次:

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

为什么在分配这个对象后立即释放它?使用完该对象后释放它。与上一点相同。

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

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

关于ios - 指向框架中泄漏的仪器——发现泄漏在其他地方(为什么?),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9446790/

相关文章:

linux - 找出 mem_map[] 数组使用的页面

java - jmap/jhat 与 jVisualVM

ios - 需要帮助修复内存泄漏 - NSMutableString

ios - 仪器 xcode4 不工作?

python - tensorflow - 内存泄漏?

ios - 如何为缩略图快速加载图像的低分辨率版本?或者简单地说,如何最好地创建缩略图?

iPhone:viewDidDisappear 的正确用法是什么?

ios - 将方法从 Objective-C 转换为 Swift 时的区别

ios - 在后台线程上保存数据时收到内存警告

ios - 自调整 UILabel 与 UIView 子类化