对 MKMapView 中的注释执行操作时泄漏

Posted

技术标签:

【中文标题】对 MKMapView 中的注释执行操作时泄漏【英文标题】:Leak when carrying out operations on annotations in an MKMapView 【发布时间】:2011-11-01 10:34:14 【问题描述】:

我有一个方法,该方法接收要在地图视图上显示的注释(自定义 PostLocationAnnotation 类),并将靠近的簇聚集在一起,输出 PostLocationAnnotations 和 LocationGroupAnnotations 的 MKAnnotation 数组(每个簇都包含一些 PostLocationAnnotations )。以下是我调用函数的方式(在“updateAnnotations”方法中,当地图的视口发生变化时调用):

[annotationsToAdd addObjectsFromArray:[ffMapView annotations]];        
[ffMapView addAnnotations:[self clusterAnnotations:annotationsToAdd WithEpsilon:20.0f andMinPts:4]];

annotationsToAdd 最初由已从服务器检索但尚未添加到地图的注释填充。因此,我将应放在地图上的注释的完整列表传递给 clusterAnnotations 方法。这是方法的主体:

- (NSArray *)clusterAnnotations:(NSArray *)annotations WithEpsilon:(float)eps andMinPts:(int)minPts

    NSMutableSet *D = [[NSMutableSet alloc] initWithCapacity:[annotations count]];
    NSMutableArray *C = [[NSMutableArray alloc] init];

    for (id <MKAnnotation> annotation in annotations)
    
        if ([annotation isKindOfClass:[PostLocationAnnotation class]])
        
             NSMutableDictionary *dictEntry = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                              annotation, @"point",
                                              [NSNumber numberWithBool:NO], @"visited",
                                              [NSNumber numberWithBool:NO], @"noise",
                                              [NSNumber numberWithBool:NO], @"clustered", nil];

            [D addObject:dictEntry];

            [dictEntry release];
         else if ([annotation isKindOfClass:[LocationGroupAnnotation class]])
        
            for (PostLocationAnnotation *location in [(LocationGroupAnnotation *)annotation locations])
            
                NSMutableDictionary *dictEntry = [NSMutableDictionary dictionaryWithObjectsAndKeys:
                                                  location, @"point",
                                                  [NSNumber numberWithBool:NO], @"visited",
                                                  [NSNumber numberWithBool:NO], @"noise",
                                                  [NSNumber numberWithBool:NO], @"clustered", nil];

                [D addObject:dictEntry];

                [dictEntry release];
            
        

    

    for (NSMutableDictionary *P in D)
    
        if ([P objectForKey:@"visited"] == [NSNumber numberWithBool:NO])
        
             [P setValue:[NSNumber numberWithBool:YES] forKey:@"visited"];

             NSMutableSet *N = [[NSMutableSet alloc] initWithSet:[self regionQueryForPoint:P andEpsilon:eps fromList:D]];

             if ([N count] < minPts)
             
                 [P setValue:[NSNumber numberWithBool:YES] forKey:@"noise"];
              else 
                 LocationGroupAnnotation *newCluster = [[LocationGroupAnnotation alloc] initWithLocations:nil];
                 [C addObject:newCluster];
                 [self expandDbscanClusterWithPoint:P andRegion:N andCluster:newCluster andEpsilon:eps andMinPts:minPts fromList:D];

                 [newCluster release];
            

            [N release];

        
    


    NSMutableArray *annotationsToAdd = [[[NSMutableArray alloc] initWithCapacity:[annotations count]] autorelease];

    for (NSMutableDictionary *P in D)
    
        if ([P objectForKey:@"clustered"] == [NSNumber numberWithBool:NO])
        
            [annotationsToAdd addObject:[P objectForKey:@"point"]];
        
    

    for (LocationGroupAnnotation *cluster in C)
    
        [cluster updateCenterCoordinate];
    

    [annotationsToAdd addObjectsFromArray:(NSArray *)C];

    [D release];
    [C release];

    return (NSArray *)annotationsToAdd;

当我运行它时,我收到一条僵尸消息,我发现删除 [D release] 修复了僵尸但会导致泄漏。查看 Instruments 可以看到内存地址首先在 clusterAnnotations 中进行了 Malloc 处理,然后被保留并释放了几次,然后被 regionQueryForPoint 保留了很多次(达到 47 个引用的峰值),然后被 clusterAnnotations 释放了两次,然后由 [NSAutoreleasePool drain] 释放,直到引用计数达到 -1 并且我收到僵尸消息错误。下面是 regionQueryForPoint 的代码:

- (NSSet *)regionQueryForPoint:(NSMutableDictionary *)P andEpsilon:(float)eps fromList:(NSMutableSet *)D

    NSMutableSet *N = [[[NSMutableSet alloc] init] autorelease];

    for (NSMutableDictionary *dictEntry in D)
    
        if ((dictEntry != P) &&
            ([[dictEntry objectForKey:@"point"] isKindOfClass:[PostLocationAnnotation class]]))
        
            CGPoint p1 = [ffMapView convertCoordinate:[[P objectForKey:@"point"] coordinate] toPointToView:self.view];
            CGPoint p2 = [ffMapView convertCoordinate:[[dictEntry objectForKey:@"point"] coordinate] toPointToView:self.view];

            float dX = p1.x - p2.x;
            float dY = p1.y - p2.y;

            if (sqrt(pow(dX,2)+pow(dY,2)) < eps)
            
                [N addObject:dictEntry];
            
        
    
    return (NSSet *)N;

从 expandDbScanClusterWithPoint 方法调用 regionQueryForPoint 时似乎会发生大量保留,因此为了完整起见,我将其包含在此处:

- (void)expandDbscanClusterWithPoint:(NSMutableDictionary *)P andRegion:(NSMutableSet *)N
                      andCluster:(LocationGroupAnnotation *)cluster
                      andEpsilon:(float)eps
                       andMinPts:(int)minPts
                        fromList:(NSMutableSet *)D


    [cluster addAnnotation:(PostLocationAnnotation *)[P objectForKey:@"point"]];
    [P setValue:[NSNumber numberWithBool:YES] forKey:@"clustered"];

    BOOL finished = NO;

    while (!finished)
    
        finished = YES;

        for (NSMutableDictionary *nextP in N)
        
            if ([nextP objectForKey:@"visited"] == [NSNumber numberWithBool:NO])
            
                [nextP setValue:[NSNumber numberWithBool:YES] forKey:@"visited"];

                NSSet *nextN = [self regionQueryForPoint:nextP andEpsilon:eps fromList:D];

                if ([nextN count] >= minPts)
                
                    [N unionSet:nextN];
                    finished = NO;
                    break;
                
            

            if ([nextP objectForKey:@"clustered"] == [NSNumber numberWithBool:NO])
            
                [cluster addAnnotation:[nextP objectForKey:@"point"]];
                [nextP setValue:[NSNumber numberWithBool:YES] forKey:@"clustered"];
            

        
    


多年来我一直在剖析这个,计算引用,观察指针和所有东西,但我就是不知道如何安全地释放这个 D 集。谁能看到我没看到的东西?

【问题讨论】:

【参考方案1】:

您似乎过度释放了dictEntry[dictEntry release];。使用dictionaryWithObjectsAndKeys 时,您将获得一个自动释放的对象。所以再次释放它会减少保留计数。

编辑:如果您不确定它是如何工作的以及何时实际保留对象,您可能需要查看memory management docs:

您使用名称以“alloc”开头的方法创建对象, “new”、“copy”或“mutableCopy”(例如,alloc、newObject 或 可变复制)。

【讨论】:

以上是关于对 MKMapView 中的注释执行操作时泄漏的主要内容,如果未能解决你的问题,请参考以下文章

iPhone应用程序中的MKMapView内存泄漏

从另一个视图中的 MKMapView 中删除注释

如何在 MKMapView 上添加的自定义注解中添加标题和副标题?

在启动时可靠地选择 MKMapView 中的注释(在 ios 6 和 ios 5.1 中)?

MKMapView 注释取消选择策略

使用Valgrind检测DPDK内存泄漏