0

I have a ccColor3B property in my class which I'd like to persist using NSCoding. How can I achieve this? NSCoder does not seem to have a method which allows it.

Rory Harvey
  • 2,579
  • 2
  • 22
  • 27

3 Answers3

1

@Justin is correct, you must encode via bytes, but I think he's over thinking things:

// encode
ccColor3B input;
[coder encodeBytes:&input length:sizeof(input) forKey:@"color"];

// decode
ccColor3B output;
const uint8_t *bytes = [coder decodeBytesForKey:@"color" returnedLength:NULL];
memcpy(&output, bytes, sizeof(output));
Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201
  • @RichardJRossIII well, i was providing an illustrative example, while trying to avoid the portability and "ABI gotchas" tome which should accompany byte-copying structures when serializing :) – justin May 29 '12 at 14:31
0

Encode by bytes, like so:

const NSUInteger BytesInCCColor = 3U;
const uint8_t bytes[BytesInCCColor] = { color.r, color.g, color.b };
[coder encodeBytes:bytes length:BytesInCCColor forKey:@"color"];

Decoding:

NSUInteger outLength = 0;
const uint8_t* const bytes =
    [coder decodeBytesForKey:@"color" returnedLength:&outLength];

if (NULL == bytes || BytesInCCColor != outLength) {
  …uh-oh…
}
else {
  color.r = bytes[0];
  color.g = bytes[1];
  color.b = bytes[2];
}
justin
  • 104,054
  • 14
  • 179
  • 226
  • I have to downvote this even though it works, because it does so with very dangerous style. You should be using sizeof(color), not a hardcoded 3. –  Jul 04 '12 at 13:05
  • @JoeWreschnig i see **zero** danger in this implementation... go ahead and *explain* that 'very dangerous style' to me. there's a good reason for using a fixed number: a 3-component byte sequence is what is encoded in my implementation, not the `color`. of course, i left extended error handling and versioning to the OP. pedantry: *your* implementation assumes memory layouts of unions in the implementation while using multiple active members; that's unspecified behavior. the standard makes no guarantee that the memory of the members are aligned or overlap. – justin Jul 04 '12 at 22:49
  • The danger is the practice of hardcoding structure sizes, rather than using sizeof(var) (or when that's not possible, sizeof(type)). As I said, there's no danger in this implementation because it is per se correct, but the idiom is bad - all it takes is someone to put a `2` instead of a `3` and it falls apart with no warning. C does guarantee type-punned unions (and iirc even pointer casts, but that interferes with compiler warnings often) of equivalent unsigned/signed types work in the way I'm using them. –  Jul 05 '12 at 07:33
  • @JoeWreschnig the structure size serves no purpose in the approach i outlined; the structure is not encoded. its *members* are encoded in an implementation/abi-independent manner. it intentionally obviates the subtleties of abi and other memory layout pitfalls. for this reason, using the structure sizes may result in incorrect values and errors when there are abi differences. (cont) – justin Jul 05 '12 at 09:29
  • @JoeWreschnig (cont) *all it takes is someone to put a 2 instead of a 3 and it falls apart with no warning*: wrong. if you make that change, you will get a build error: "excess elements in array initializer". although we don't agree, i *do* appreciate the explanation for the downvote. cheers. – justin Jul 05 '12 at 09:30
0

Better than using ad hoc type-unsafe conversions is to implement a category on NSCoder that understands how to handle it:

@implementation NSCoder (cocos2d)

- (void)encodeUInt32:(uint32_t)i forKey:(NSString *)key {
    union { int32_t s; uint32_t u; } v;
    v.u = i;
    [self encodeInt32:v.s forKey:key];
}

- (uint32_t)decodeUInt32ForKey:(NSString *)key {
    union { int32_t s; uint32_t u; } v;
    v.s = [self decodeInt32ForKey:key];
    return v.u;
}

- (void)encodeColor3B:(ccColor3B)color forKey:(NSString *)key {
    /* Storing 0xFF as the low 8 bits allows us to read/write ccColor3B
       and ccColor4B interchangeably. */
    uint32_t rgba = (color.r << 24) | (color.g << 16) | (color.b << 8) | 0xFF;
    [self encodeUInt32:rgba forKey:key];
}

- (ccColor3B)decodeColor3BForKey:(NSString *)key {
    ccColor3B c;
    uint32_t rgba = [self decodeUInt32ForKey:key];
    c.r = rgba >> 24;
    c.g = rgba >> 16;
    c.b = rgba >> 8;
    return c;
}

@end

Then you can just do:

self.color = [decoder decodeColor3BForKey:@"color"];

and

[encoder encodeColor3B:self.color forKey:@"color"];