在iOS6中将地图上的引脚丢弃后崩溃

时间:2013-05-22 21:27:07

标签: objective-c ios6 memory-leaks mapkit

我正在尝试学习如何在iOS 6中的地图上放置引脚。我有一个编译和运行但显然会泄漏内存的代码 - 但是当我释放(或自动释放)mapData对象时,我的应用程序崩溃了。错误是

0x1b7ac0的实例AddressAnnotation已取消分配,而键值观察者仍在其中注册。观察信息被泄露,甚至可能被错误地附加到其他物体上。在NSKVODeallocateBreak上设置一个断点,在调试器中停止。

关于MapKit还有一个关于这个错误的早期帖子:
Setting breakpoint at NSKVODeallocateBreak

但它对我没有帮助:
首先,我真的不明白答案,但似乎答案与我的问题无关,因为我没有以任何方式设置观察者(我知道,那就是!)例如,我的代码中没有任何地方我有线

[addressAnnotation addObserver:self forKeyPath:kSelectedAnnotationObserverKeyPath options:NSKeyValueObservingOptionNew context:@"selectedOrDeselected"];

或其他远程相似的东西,这被认为是问题所在。

话虽如此,我还应该说我并不真正理解观察者的概念 - 我当然创建了一个自定义类MapData,这是一个NSObject <MKAnnotation>我认为这个也可能是问题的根源。但我基本上是笨拙的。

我试图设置建议的符号断点,但它对我没有帮助:我看到我有BAD ACCESS条件,但这是我真正理解的!

我写的代码是:

- (void) showRecordsOnMap
{
    NSMutableArray *projectMapAnnotationsArray;
    projectMapAnnotationsArray = [[NSMutableArray alloc] init];

    int i = 0;
    for (i = 0; i < [currentProject.recordArray count]; i++)
    {
        Record *record = [[[Record alloc] init]autorelease];
        record = [currentProject.recordArray objectAtIndex:i];

        CLLocationCoordinate2D newCoordinate;
        newCoordinate.latitude = record.latitude;
        newCoordinate.longitude = record.longitude;
        int tag = 0;
        NSString *title;
        title = [[[NSString alloc] init] autorelease];
        title =  [NSString stringWithFormat:@"Record %d",record.record_ID];
        NSString *subtitle;
        subtitle = [[[NSString alloc] init] autorelease];
        subtitle =  [NSString stringWithFormat:@"Record %d",record.record_ID];

        MapData *mapData =[[MapData alloc] initWithCoordinate:newCoordinate withTag:tag withTitle:title withSubtitle:title];

        [projectMapAnnotationsArray addObject:mapData];

        //[mapData release];

    }

    [projectMap addAnnotations:projectMapAnnotationsArray];
    [projectMapAnnotationsArray release];

}

然后是下一个需要的位

- (MKAnnotationView *) mapView:(MKMapView *)mapView
             viewForAnnotation:(MapData *)annotation
{       
    static NSString *record = @"record";

    //the result of the call is being cast (MKPinAnnotationView *) to the correct
    //view class or else the compiler complains
    MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[projectMap
                                                                  dequeueReusableAnnotationViewWithIdentifier:record];
    if(annotationView == nil)
    {
        annotationView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:record] autorelease];
    }

    //if((annotation).tag == 2) annotationView.pinColor = MKPinAnnotationColorRed;
    //else annotationView.pinColor = MKPinAnnotationColorGreen;

    annotationView.pinColor = MKPinAnnotationColorGreen;
    //pin drops when it first appears
    annotationView.animatesDrop=TRUE;

    //tapping the pin produces a gray box which shows title and subtitle
    annotationView.canShowCallout = YES;

    return annotationView;
}

只要未发布mapData对象,此代码就会运行。但显然我需要释放它。作为另一条线索,如果我取消注释

// if((annotation).tag == 2) annotationView.pinColor = MKPinAnnotationColorRed;
// else annotationView.pinColor = MKPinAnnotationColorGreen;

我收到另一个错误:

  

[MKUserLocation tag]:无法识别的选择器发送到实例0x9fcb010   2013-05-22 23:05:13.726 Geo360 [1175:c07] ***因未捕获的异常'NSInvalidArgumentException'而终止应用,原因:'-[MKUserLocation tag]:无法识别的选择器已发送到实例0x9fcb010'

但在我看来,第二个错误是我更简单的愚蠢,我至少知道如何寻找。但是错误“class AddressAnnotation”让我彻底迷失了。非常感谢任何帮助!


修改

大家好 - 感谢您抽出宝贵时间提供帮助。我还是很困惑。附件是AnnaKarenina建议的MapData对象的代码。 Verbumdei建议我将ViewDidLoad方法中的数组作为一个强大的属性 - 我曾经玩过但我也希望能够使用可能包含更多数据或更少数据的数组刷新地图引脚,所以在我看来我需要每次都重新制作阵列。也许不是? AnnaKarenina建议在MapData中可能存在一个发布问题,现在我看着它我有点怀疑我没有发布标签 - 但另一方面,这样做会产生警告!

再次感谢您的帮助......仍然没有解决。

MapData.h

#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>

@interface MapData : NSObject <MKAnnotation>
{
    NSString *_title;
    NSString *subtitle;
    NSUInteger tag;
    CLLocationCoordinate2D _coordinate;
}

@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *subtitle;
@property(nonatomic) NSUInteger tag;
// Getters and setters

- (id)initWithCoordinate:(CLLocationCoordinate2D)c withTag:(NSUInteger)t withTitle:(NSString *)tl withSubtitle:(NSString *)s;
@end

MapData.m

#import "MapData.h"

@implementation MapData

@synthesize coordinate;
@synthesize title;
@synthesize subtitle;
@synthesize tag;

-(id)initWithCoordinate:(CLLocationCoordinate2D)c withTag:(NSUInteger)t withTitle:(NSString *)tl withSubtitle:  (NSString *)s
{
    if(self = [super init])
    {
        coordinate = c;
        tag = t;
        title = tl;
        subtitle = s;
    }
    return self;        
}

- (void) dealloc
{
    [title release];
    [subtitle release];
    [super dealloc];
}

@end

2 个答案:

答案 0 :(得分:1)

关于EXC_BAD_ACCESS 的崩溃,这很可能是由于MapData initWithCoordinate方法中的此代码所致:

title = tl;
subtitle = s;

通过这种方式初始化实例变量,MapData对象不会保留字符串。当您调用[mapData release]时,字符串将被释放,然后当地图视图尝试访问注释的标题和副标题时,它会崩溃。

将初始化更改为:

title = [tl copy];
subtitle = [s copy];

并取消评论[mapData release];


关于“释放键值观察者仍然已注册...”警告消息,这可能是由于注释使用的坐标无效(请参阅Warning in Custom Map Annotations iPhone)。对于每个注释,请确保纬度为-90到90,经度为-180到180.


关于“[MKUserLocation标记]:无法识别的选择器”崩溃,这与上述两个问题无关。发生此错误的原因是所有注释的地图视图调用viewForAnnotation委托方法 - 而不仅仅是您添加的注释。这意味着它也被称为地图视图本身创建的用户位置蓝点。当您的自定义注释类型为MKUserLocation时,该用户位置注释的类型为MapData。当地图视图为用户位置调用viewForAnnotation时,该代码崩溃,因为MKUserLocation类没有tag属性。

处理此问题的最简单方法是在viewForAnnotation方法的顶部,检查注释是否为MKUserLocation类型,并为视图返回nil(告诉地图视图显示默认视图,该视图是用户位置的蓝点。)

另外,请勿将annotation方法中viewForAnnotation参数的类型声明更改为自定义类型(即使它“有效”)。如上所述,该方法适用于MKUserLocation以及您的自定义类(或类)。保持泛型类型id<MKAnnotation>,这意味着“实现MKAnnotation协议的对象”。然后,要访问自定义类的属性,请将annotation强制转换为自定义类类型。

所以viewForAnnotation方法的顶部应该是这样的:

- (MKAnnotationView *)mapView:(MKMapView *)mapView 
    viewForAnnotation:(id<MKAnnotation>)annotation
{
    if ([annotation isKindOfClass:[MKUserLocation class]])
        return nil;  //show standard blue dot view for user location

然后改变这些行:

if((annotation).tag == 2) annotationView.pinColor = MKPinAnnotationColorRed;
else annotationView.pinColor = MKPinAnnotationColorGreen;

到此:

if ([annotation isKindOfClass:[MapData class]])
{
    MapData *mapData = (MapData *)annotation;
    if (mapData.tag == 2)
        annotationView.pinColor = MKPinAnnotationColorRed;
    else
        annotationView.pinColor = MKPinAnnotationColorGreen;
}


还有其他一些事情(与崩溃或错误无关):

  • 这种模式错了:

    Record *record = [[[Record alloc] init]autorelease];
    record = [currentProject.recordArray objectAtIndex:i];
    

    alloc + init + autorelease是没有意义的,因为该变量会立即重新分配给另一个已经分配的对象。相反,只需声明并分配局部变量:

    Record *record = [currentProject.recordArray objectAtIndex:i];
    

    同样适用于titlesubtitle在同一方法中的设置方式。

  • showRecordsOnMap中,tag始终为0。也许您的代码尚未完成,但请确保为每个注释设置不同的值。如果您将所有注释的标记保留为零,则放入viewForAnnotation以根据标记设置图钉颜色的代码将无法按预期工作。

  • 在这一行中,您将为标题和副标题参数传递title

    MapData *mapData =[[MapData alloc] initWithCoordinate:newCoordinate 
        withTag:tag withTitle:title withSubtitle:title];
    
  • viewForAnnotation中,在将现有注释视图出列后,您应该将重用视图的annotation属性更新为当前属性(添加else部分):< / p>

    if (annotationView == nil)
    {
        annotationView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:record] autorelease];
    }
    else
    {
        //We're re-using a view from another annotation
        //that's no longer on screen.
        //Update the view's annotation to the current one...
        annotationView.annotation = annotation;
    }
    

答案 1 :(得分:0)

虽然注释显示在地图中,但您不得释放MapData对象。如果要释放MapData对象,则需要首先从地图中删除注释。

我建议你将projectMapAnnotationsArray作为视图控制器的强大属性。在viewDidLoad方法中分配它。然后,您可以在视图控制器的dealloc方法中释放它。

showRecordsOnMap方法中,您只需将对象添加到projectMapAnnotationsArray,而不释放数组本身。