4

I want to use Mantle framework (https://github.com/github/Mantle) to support NSCoding for my class with struct property:

typedef struct {
    int x;
    int y;
} MPoint;

typedef struct {
    MPoint min;
    MPoint max;
} MRect;


@interface MObject : MTLModel

@property (assign, nonatomic) MRect rect;

@end

@implementation MObject
@end

But when I tried to [NSKeyedArchiver archiveRootObject:obj toFile:@"file"]; its crashed in MTLModel+NSCoding.m, in - (void)encodeWithCoder:(NSCoder *)coder on line

case MTLModelEncodingBehaviorUnconditional:
    [coder encodeObject:value forKey:key];

Does Mantle supports c-struct encoding (and also decoding) or I've need to custom implementing NSCoding protocol for such classes?

podkovyr
  • 135
  • 3
  • 11

2 Answers2

4

My original data structure is an XML (yeah, I know):

  ...
  <Lat>32.062883</Lat>
  <Lot>34.782904</Lot>
  ...

I used MTLXMLAdapter based on KissXML, but you can see how it's applicable to any other serializer.

+ (NSValueTransformer *)coordinateXMLTransformer {
    return [MTLValueTransformer reversibleTransformerWithBlock:^id(NSArray *nodes) {
        CLLocationCoordinate2D coordinate;
        for (DDXMLNode *node in nodes) {
            if ([[node name] isEqualToString:@"Lat"]) {
                coordinate.latitude = [[node stringValue] doubleValue];
            } else if ([[node name] isEqualToString:@"Lot"]) {
                coordinate.longitude = [[node stringValue] doubleValue];
            }

        }
        return [NSValue value:&coordinate
                 withObjCType:@encode(CLLocationCoordinate2D)];
    }];
}

You can add a reverseBlock if needed.

Sash Zats
  • 5,376
  • 2
  • 28
  • 42
4

It was easier than I thought:

  1. Exclude property in +encodingBehaviorsByPropertyKey
  2. Manual encode/encode excluded property

Sample:

#pragma mark - MTLModel + NSCoding

- (id)initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    if (self) {
        self.rect = [[self class] mRectFromData:[coder decodeObjectForKey:@"rectData"]];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
    [super encodeWithCoder:coder];

    [coder encodeObject:[[self class] dataFromMRect:self.rect] forKey:@"rectData"];
}

+ (NSDictionary *)encodingBehaviorsByPropertyKey {
    NSDictionary *excludeProperties = @{
                                        NSStringFromSelector(@selector(rect)): @(MTLModelEncodingBehaviorExcluded)
                                        };
    NSDictionary *encodingBehaviors = [[super encodingBehaviorsByPropertyKey] mtl_dictionaryByAddingEntriesFromDictionary:excludeProperties];
    return encodingBehaviors;
}

#pragma mark - MRect transformations

+ (MRect)mRectFromData:(NSData *)rectData {
    MRect rect;
    [rectData getBytes:&rect length:sizeof(rect)];
    return rect;
}

+ (NSData *)dataFromMRect:(MRect)rect {
    return [NSData dataWithBytes:&rect length:sizeof(rect)];
}
Frank Schmitt
  • 25,648
  • 10
  • 58
  • 70
podkovyr
  • 135
  • 3
  • 11
  • A Reversible Value Transformer would be the correct solution for the said property, like in Sasha Zats Example – GreatWiz May 29 '14 at 12:11
  • @GreatWiz That will work for JSON/XML encoding, but won't get called for during NSCoding. Using intermediate values to represent a struct seems to be [the preferred approach](https://github.com/Mantle/Mantle/issues/44). – Frank Schmitt Oct 06 '14 at 21:30
  • @fa the answer you linked speaks about external representations – GreatWiz Oct 29 '14 at 13:07
  • @FrankSchmitt the answer you linked speaks about multiple external representations that map to a single struct (that is lat long to coord). For this example a ValueTransformer would allow you to write custom code to transform between a struct and an object. It's the same as this answer, but using the mantle toolset/mindset. P.S. Sorry for the duplicate post – GreatWiz Oct 29 '14 at 13:13
  • @GreatWiz Mantle tries to encode the struct directly when archiving, and the `NSKeyedArchiver` throws an exception. Or is there a trick to getting Mantle to clue in that it should use the value transformer for NSCoding (*not* JSON/XML encoding)? – Frank Schmitt Oct 31 '14 at 22:46
  • @FrankSchmitt you are right, I missed the point that it's a direct encoding without the dictionary stage (because a dictionary isn't always JSON). In this case, this solution is almost correct. The only concern is that encoding the struct directly into NSData isn't recommended. Instead it should be encoded by its fields, to prevent compiler dependancy ( see goo.gl/8PeXGx) another option is to exclude the struct like in this example, and instead add a calculated, lazy loaded property that uses the said value transformer in the getter, and the setter uses it to and sets the struct property. – GreatWiz Nov 09 '14 at 18:47