Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I encode an NSValue in an NSKeyedArchiver?

I was trying to encode an MKMapView center and span into an NSKeyedArchiver for state preservation. I found a couple of handy new MapKit NSValue additions, valueWithMKCoordinate: and valueWithMKCoordinate:. Trying to encode these into the keyed archiver failed:

- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
    NSValue *mapCenterValue = [NSValue valueWithMKCoordinate:mapView.centerCoordinate];
    NSValue *mapSpanValue = [NSValue valueWithMKCoordinateSpan:mapView.region.span];
    [coder encodeObject:mapCenterValue forKey:kMapCenter];
    [coder encodeObject:mapSpanValue forKey:kMapSpan];
}

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSKeyedArchiver encodeValueOfObjCType:at:]: this archiver cannot encode structs'

I understand that the solution to this problem is to just encode the individual doubles into four separate keys.

My question is why does this happen. An NSValue is an object, so why is it telling me "this archiver cannot encode structs"

like image 841
nevan king Avatar asked Aug 28 '13 10:08

nevan king


2 Answers

According to the documentation of the NSKeyedArchiver class,

A keyed archive differs from a non-keyed archive in that all the objects and values encoded into the archive are given names, or keys.

In order to archive elements of a structand give keys to them NSKeyedArchiver would need metadata to know where each field of a struct is located, and what are the names of these fields. The @encode stored with NSValue gives it enough information about the layout of a struct, but the information about the names of each field is missing.

Since there is no metadata about the names of the fields in a struct, it would be impossible to archive the data in such a way as to ensure proper un-archiving. That is why NSKeyedArchiver must refuse to archive NSValues with embedded C structs.

like image 130
Sergey Kalinichenko Avatar answered Oct 13 '22 21:10

Sergey Kalinichenko


I ran into the same problem and decided the easiest solution would just be to be boring and encode all the values individually:

- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
    MKCoordinateRegion region = [self.mapView region];
    [coder encodeDouble:region.center.latitude forKey:RWSMapCenterLatitude];
    [coder encodeDouble:region.center.longitude forKey:RWSMapCenterLongitude];
    [coder encodeDouble:region.span.latitudeDelta forKey:RWSMapCenterLatitudeDelta];
    [coder encodeDouble:region.span.longitudeDelta forKey:RWSMapCenterLongitudeDelta];

    [super encodeRestorableStateWithCoder:coder];
}

- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
    MKCoordinateRegion region;
    CLLocationCoordinate2D center;
    center.latitude = [coder decodeDoubleForKey:RWSMapCenterLatitude];
    center.longitude = [coder decodeDoubleForKey:RWSMapCenterLongitude];
    region.center = center;
    MKCoordinateSpan span;
    span.latitudeDelta = [coder decodeDoubleForKey:RWSMapCenterLatitudeDelta];
    span.longitudeDelta = [coder decodeDoubleForKey:RWSMapCenterLongitudeDelta];
    region.span = span;

    self.mapView.region = region;

    [super decodeRestorableStateWithCoder:coder];
}
like image 23
Samuel Goodwin Avatar answered Oct 13 '22 20:10

Samuel Goodwin