Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSKeyedArchiver fails with CLLocationCoordinate2D structs. Why?

I don't understand why I can archive CGPoint structs but not CLLocationCoordinate2D structs. What's the difference to the archiver?

Platform is iOS. I'm running in the simulator and haven't tried on the device.

// why does this work:
NSMutableArray *points = [[[NSMutableArray alloc] init] autorelease];
CGPoint p = CGPointMake(10, 11);
[points addObject:[NSValue valueWithBytes: &p objCType: @encode(CGPoint)]];
[NSKeyedArchiver archiveRootObject:points toFile: @"/Volumes/Macintosh HD 2/points.bin" ];

// and this doesnt work:
NSMutableArray *coords = [[[NSMutableArray alloc] init] autorelease];
CLLocationCoordinate2D c = CLLocationCoordinate2DMake(121, 41);
[coords addObject:[NSValue valueWithBytes: &c objCType: @encode(CLLocationCoordinate2D)]];
[NSKeyedArchiver archiveRootObject:coords toFile: @"/Volumes/Macintosh HD 2/coords.bin" ];

I get a crash on the 2nd archiveRootObject and this message is printed to the console:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSKeyedArchiver encodeValueOfObjCType:at:]: this archiver cannot encode structs'
like image 709
TomSwift Avatar asked Sep 05 '12 23:09

TomSwift


2 Answers

OK, Tom, are you ready for some geek-ness? I'm an "older" guy in this world of young whippersnappers. However, I remember a few things about C, and I'm just a geek at heart.

Anyway, there is a subtle difference between this:

typedef struct { double d1, d2; } Foo1;

and this:

typedef struct Foo2 { double d1, d2; } Foo2;

The first is a type alias to an anonymous structure. The second is a type alias to struct Foo2.

Now, the documentation for @encode says that the following:

typedef struct example {
    id   anObject;
    char *aString;
    int  anInt;
} Example;

will result in {example=@*i} for both @encode(example) or @encode(Example). So, this implies that @encode is using the actual struct tag. In the case of a typedef that creates an alias to an anonymous struct, it looks like @encode always returns ?'

Check this out:

NSLog(@"Foo1: %s", @encode(Foo1));
NSLog(@"Foo2: %s", @encode(Foo2));

Anyway, can you guess how CLLocationCoordinate2D is defined? Yep. You guessed it.

typedef struct {
CLLocationDegrees latitude;
CLLocationDegrees longitude;
} CLLocationCoordinate2D;

I think you should file a bug report on this. Either @encode is broken because it does not use alias typedefs to anonymous structs, or CLLocationCoordinate2D needs to be fully typed so it is not an anonymous struct.

like image 73
Jody Hagins Avatar answered Nov 03 '22 23:11

Jody Hagins


To get around this limitation until the bug is fixed, simply break down the coordinates and reconstruct:

- (void)encodeWithCoder:(NSCoder *)coder
{
    NSNumber *latitude = [NSNumber numberWithDouble:self.coordinate.latitude];
    NSNumber *longitude = [NSNumber numberWithDouble:self.coordinate.longitude];
    [coder encodeObject:latitude forKey:@"latitude"];
    [coder encodeObject:longitude forKey:@"longitude"];
    ...

- (id)initWithCoder:(NSCoder *)decoder
{
    CLLocationDegrees latitude = (CLLocationDegrees)[(NSNumber*)[decoder decodeObjectForKey:@"latitude"] doubleValue];
    CLLocationDegrees longitude = (CLLocationDegrees)[(NSNumber*)[decoder decodeObjectForKey:@"longitude"] doubleValue];
    CLLocationCoordinate2D coordinate = (CLLocationCoordinate2D) { latitude, longitude };
    ...
like image 4
David James Avatar answered Nov 03 '22 22:11

David James