How do I store a NSUInteger
using the NSCoding
protocol, given that there is no method on NSCoder
like -encodeUnsignedInteger:(NSUIn
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.
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.
NSNumber has a lot of methods to store/retrieve different sized and signed types. It is the easiest solution to use and doesn't require any byte management like other answers suggest.
Here is the types according to Apple documentation on NSNumber:
+ (NSNumber *)numberWithUnsignedInteger:(NSUInteger)value
- (NSUInteger)unsignedIntegerValue
Yes your code example is the best way to encode/decode the NSUInteger. I would recommend using constants for the key values, so you don't mistype them and introduce archiving bugs.
static NSString * const kCountKey = @"CountKey";
@interface MyObject : NSObject <NSCoding> {
NSUInteger count;
}
@end
@implementation MyObject
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:[NSNumber numberWithUnsignedInteger:count] forKey:kCountKey];
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self != nil) {
count = [[decoder decodeObjectForKey:kCountKey] unsignedIntegerValue];
}
return self;
}
@end
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