1

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

0x1b7ac0一个类的实例AddressAnnotation被解除分配,而键值观察者仍向其注册。观察信息被泄露,甚至可能被错误地附加到其他对象上。在调试器中设置断点NSKVODeallocateBreak以在此处停止。

有一篇关于这个错误的较早帖子,也是关于 MapKit:
在 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 标签]:无法识别的选择器发送到实例 0x9fcb010 2013-05-22 23:05:13.726 Geo360[1175:c07] *** 由于未捕获的异常而终止应用程序'NSInvalidArgumentException',原因:'-[MKUserLocation tag]:无法识别的选择器发送到实例0x9fcb010'

但在我看来,这第二个错误对我来说是一个更简单的愚蠢,我至少知道如何去寻找。但是错误“类AddressAnnotation”让我完全迷失了。任何帮助是极大的赞赏!


编辑:

大家好——感谢您抽出宝贵时间提供帮助。我仍然很困惑。附件是 AnnaKarenina 建议的 MapData 对象的代码。Verbumdei 建议我将数组放在 ViewDidLoad 方法中作为一个强大的属性——我已经玩过了,但我也希望能够用一个可能有更多数据或更少数据的数组来刷新地图图钉,所以在我看来我每次都需要重新制作阵列。也许不是?AnnaKarenina 建议 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
4

2 回答 2

1

关于 EXC_BAD_ACCESS 的崩溃,这很可能是由于 MapDatainitWithCoordinate方法中的这段代码:

title = tl;
subtitle = s;

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

将初始化更改为:

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

并取消注释[mapData release];


关于“在键值观察者仍在注册时释放...”警告消息,这可能是由于注释使用的坐标无效(请参阅自定义地图注释 iPhone 中的警告)。对于每个注释,确保纬度从 -90 到 90,经度从 -180 到 180。


关于“[MKUserLocation tag]: unrecognized selector” crash,这与上述两个问题无关。发生此错误是因为地图视图为所有viewForAnnotation注释调用了委托方法——而不仅仅是您添加的那些。这意味着它也被称为地图视图本身创建的用户位置蓝点。该用户位置注释属于 type而您的自定义注释属于 type 。当地图视图调用用户位置时,该代码会崩溃,因为该类没有属性。 MKUserLocationMapDataviewForAnnotationMKUserLocationtag

处理这个问题的最简单方法是在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以相同的方法设置。

  • showRecordsOnMaptag总是0。也许您的代码尚未完成,但请确保为每个注释设置不同的值。如果您将所有注释的标签保留为零,则您输入viewForAnnotation的基于标签设置图钉颜色的代码将无法按预期工作。

  • 在这一行中,您同时传递title了 title 和 subtitle 参数:

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

    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;
    }
    
于 2013-05-23T13:56:53.773 回答
0

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

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

在该showRecordsOnMap方法中,您可以只将对象添加到 projectMapAnnotationsArray 而无需释放数组本身。

于 2013-05-23T00:08:47.207 回答