views:

70

answers:

3

How do I store a NSUInteger using the NSCoding protocol, given that there is no method on NSCoder like -encodeUnsignedInteger:(NSUInteger)anInt forKey:(NSString *)aKey like there is for NSInteger?

The following works, but is this the best way to do this? This does needlessly create objects.

@interface MYObject : NSObject <NSCoding> {
    NSUInteger count;
}  

- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeObject:[NSNumber numberWithUnsignedInteger:count] forKey:@"count"];
}

- (id)initWithCoder:(NSCoder *)decoder {
    self = [super init];
    if (self != nil) {
        count = [[decoder decodeObjectForKey:@"count"] unsignedIntegerValue];
    }
    return self;
}
A: 

You're going to want to use NSCoder's

encodeInt:ForKey:

and

decodeIntForKey:

methods. So, in your case you would need :

- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeInt:count forKey:@"count"];
}

- (id)initWithCoder:(NSCoder *)decoder {
    self = [super init];
    if (self != nil) {
        count = [decoder decodeIntForKey:@"count"];
    }
    return self;
}

Hope that helps, also, there's plenty more encode "encode" methods, take a look at the documentation for NSCoder :D

Matt Egan
I am aware of that method (and with the documentation). My question is wether this would be safe, given that `int` and `NSUInteger` are not the same thing.
Johan Kool
@Peter That's what I thought. Can I encode a `NSUInteger` without wrapping it in a `NSNumber` or losing info?
Johan Kool
Yes. Technically NSUInteger is simply an alias for an unsigned integer, that's what the "U" stands for. Again, yes, it is safe to use encodeInt: with a NSUInteger.
Matt Egan
@Matt Except that `NSUInteger` can also be an `unsigned long`...
Johan Kool
Johan Kool: I deleted my comment upon realizing that NSKeyedArchiver emits plist data and encodes integers as integer number elements in the plist. Of course, both emitting integer elements and the alternative bring other problems; see my answer.
Peter Hosey
Ah, that's unfortunate. Well, I learned something today, didn't I? :D
Matt Egan
+1  A: 

That is the best way to do it. It does seem like needless work, but there's no other type available that can portably represent the entire range of values NSUInteger can hold.

Chuck
This answer conflicts directly with what Matt Egan said in his answer/comment. I am thinking you are right though.
Johan Kool
+2  A: 

The problem is that NSUInteger may not be the same size as an unsigned int. On many (if not most by now) Mac OS X machines, NSUInteger will be an unsigned long, which will be twice as big. On the other hand, a keyed archiver should handle that without a problem, since it builds a dictionary.

Further complicating matters is the fact that NSCoder doesn't have any methods to deal with unsigned types. I can't think of how this would cause any data loss, but it would require some ugly casts, plus it just feels dirty.

If you want to insist upon an unsigned type, the simplest way would be to encode the raw bytes (preferably in network byte order using htonl and ntohl) in the largest type available (unsigned long long) using encodeBytes:length:forKey: and decodeBytesForKey:returnedLength:. For maximum safety, you should check the length of what you decoded and cast the pointer (or use a union) to extract the correct-sized type.

The drawback of this is that the value will be represented in the output as data, not an integer. This mainly matters only if somebody decides to read in the raw plist data for your archive instead of using the keyed unarchiver like you do, and even then only to them. The other cases where it might matter is if Apple (or you) should ever switch to an architecture that has even larger integer types, types whose size in bits is not a power of two (there's at least one old platform where a word was 24 bits), or types with an unusual layout (not big- or little-endian).

As for your NSNumber solution: You might want to crack open your archive in Property List Editor and see what it emitted. If the output contains an integer element, then it's the same as using encodeInteger:forKey:. If the output contains a data element, then it's the same as the solution I mentioned above. To be thorough, you should check the output from every architecture you support.

Peter Hosey
I don't *think* it's necessarily true that if the output contains an integer, it's the same as `encodeInteger:forKey:`. Plist integers are infinite-length strings, while `encodeInteger:forKey:` necessarily converts the argument something in the signed integer range.
Chuck
@Chuck: Just checked. The NSNumber solution does handle numbers above `NSIntegerMax` correctly. One pitfall, though: In 32-bit mode, reading in a number greater than the 64-bit `NSIntegerMax` will bring it back as 0. @Johan Kool: You may want to use `encodeInt64:forKey:`/`decodeInt64ForKey:` instead.
Peter Hosey