10

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'
nevan king
  • 112,709
  • 45
  • 203
  • 241
TomSwift
  • 39,369
  • 12
  • 121
  • 149

3 Answers3

20

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.

Jody Hagins
  • 27,943
  • 6
  • 58
  • 87
  • Thanks; that's the perfect explanation! I'll reference it in my radar report :) – TomSwift Sep 06 '12 at 13:52
  • it is now defined as: struct CLLocationCoordinate2D { CLLocationDegrees latitude; CLLocationDegrees longitude; }; typedef struct CLLocationCoordinate2D CLLocationCoordinate2D; so I guess @encode is broken. – malhal Sep 29 '20 at 16:57
4

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 };
    ...
David James
  • 2,430
  • 1
  • 26
  • 35
0

It's because @encode chokes on CLLocationCoordinate2D

NSLog(@"coords %@; type: %s", coords, @encode(CLLocationCoordinate2D)); yields coords ( "<00000000 00405e40 00000000 00804440>" ); type: {?=dd}

Clay
  • 319
  • 2
  • 15
  • 1
    okay, that's interesting. so @encode gets that it's a structure with two doubles but misses the type name. How come? And really, why does the typename matter for an encoded struct? – TomSwift Sep 06 '12 at 00:28
  • I think the encode generates a string that's used to do something like `[[NSClassFromString(@encode(name)) alloc] init]` or some similar black magic to find the function that generates `CLLocationCoord` or `NSPoint` or whatnot. – Clay Sep 06 '12 at 12:52