NSCode: encoder and decoder for primitive types
Asked Answered
G

3

6

I was trying to create a generic encoder and decoder for my model classes. I was trying to find a way to call the "encode method" for all types of properties, either objects (NSString, NSNumber, NSArray, etc...) and primitive types. And I saw someone doing the following. And I was wondering if this would a correct way to do it.

Properties:

@property (assign,nonatomic) int integerP;
@property (assign,nonatomic) float floatP;
@property (assign,nonatomic) BOOL boolP;

Enconder and Decoder Code:

- (void)encodeWithCoder:(NSCoder *)encoder
{
    id object2 = [self valueForKey:@"integerP"];
    id object3 = [self valueForKey:@"floatP"];
    id object4 = [self valueForKey:@"boolP"];


    [encoder encodeObject:object2 forKey:@"integerP"];
    [encoder encodeObject:object3 forKey:@"floatP"];
    [encoder encodeObject:object4 forKey:@"boolP"];

    //[self setValue:[NSNumber numberWithInt:90] forKey:@"heightR"];

    //NSLog(@"%@",[self valueForKey:@"heightR"]);


}

- (id)initWithCoder:(NSCoder *)decoder
{
    self = [super init];
    if( self != nil )
    {

        id object2 = [decoder decodeObjectForKey:@"integerP"];
        [self setValue:object2 forKey:@"integerP"];
        id object3 = [decoder decodeObjectForKey:@"floatP"];
        [self setValue:object3 forKey:@"floatP"];
        id object4 = [decoder decodeObjectForKey:@"boolP"];
        [self setValue:object4 forKey:@"boolP"];

    }
    return self;
}

I was not sure if this is a correct way, or if other program or object could write in the same memory space of the primitive properties. If the method above is correct, what is the difference between the above and this:

The way I thought was correct:

- (void)encodeWithCoder:(NSCoder *)encoder
{


    [encoder encodeInt:integerP forKey:@"integerP"];
    [encoder encodeFloat:floatP forKey:@"floatP"];
    [encoder encodeBool:boolP forKey:@"boolP"];

    //[self setValue:[NSNumber numberWithInt:90] forKey:@"heightR"];

    //NSLog(@"%@",[self valueForKey:@"heightR"]);


}

- (id)initWithCoder:(NSCoder *)decoder
{
    self = [super init];
    if( self != nil )
    {
        integerP = [decoder decodeIntForKey:@"integerP"];
        floatP = [decoder decodeFloatForKey:@"floatP"];
        boolP = [decoder decodeBoolForKey:@"boolP"];


    }
    return self;
}

I tested and both methods returned the correct values.

Grey answered 28/2, 2012 at 11:29 Comment(1)
I don't understand. There are already methods for encoding/decoding primitive types, why you wouldn't use them? For me, the second method is more correct.Elsey
S
8

Both methods will work.

The first is particularly clever, being that valueForKey: will always return an NSObject, even when the value is actually a primitive, so float/int/bool types will be wrapped in an NSNumber automatically by the KVC getter, and unwrapped in the KVC setter.

It might be possible to use this to implement some sort of generic encode/decode functions that operate on an array of property keys.

However, the second example is the standard way to do it, and the way I'd probably recommend. Sometimes you've got to write boilerplate code!

Saeger answered 28/2, 2012 at 11:54 Comment(3)
Good to know that both would work and it is ok to use. But, why would you recommend the second one? Do you know any kind of performance gain if you use the second method? - For now, I have 2 choices to write my generic enconde/decode, either use the first encode method posted above or use an NSDictionary that would contain all my primitive properties. Which way would you recommend?Grey
I don't know for sure, but I believe there would be negligible performance difference. If you were storing the primitives in an NSDictionary, you'd have to convert them to NSNumbers before doing so anyway, so it's effectively the same thing. My recommendation would be to just write the encode/decode manually, but if you want to try a generic encode/decode, the first would be the best option. However, if performance is a big issue, you really should consider moving to a Core Data model. NSCoder is pretty slow at encoding large object trees.Saeger
Yeah, I imagine that might be a small performance difference as well. For now, I think I will write the generic encoder/decoder using the first method. I dont believe it will cause me any big performance difference and will save me a lot of time for other projects. And yeah, I will use Core Data model for large objects, I am only planning to use NSCoder for small prefs objects. But Thanks!Grey
S
1

You first method looks very strange!

In Objective-C float/int/BOOL are not "Object". You can convert them into a NSNumber by calling:

NSNumber *aNumber = [NSNumber numberWithInt:integerP];

But your second solution looks fine.
Only use decodeObjectForKey for Objects like NSArray, etc. or your own class (where you also need to add the coding/decoding methods!)

And leave your fingers from setValue and valueForKey.

Steenbok answered 28/2, 2012 at 11:42 Comment(0)
F
1

Try this:

BaseModel.h

@interface BaseModel : NSObject<NSCoding>
@end

BaseModel.m

- (NSArray *)keysForEncoding
{
    [NSException raise:@"keysForEncoding" format:@"keysForEncoding must be implemented in child class!"];
    //example implementation in child class:
    //return @[@"airtime", @"channelID", @"duration", @"programID", @"shortTitle"];
    return nil;
}


-(void)encodeWithCoder:(NSCoder *)aCoder
{
    for(NSString* key in [self keysForEncoding])
    {
        [aCoder encodeObject:[self valueForKey:key] forKey:key];
    }
}

-(id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        for (NSString* key in [self keysForEncoding]) {
            [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
        }
    }
    return self;
}

Then derive from that base class your class with actual data.

Example of simple class:

EPGData.h

    @interface EPGData : BaseModel
    @property(nonatomic, assign) NSTimeInterval airtime; //date in 1970 format
    @property(nonatomic, assign) int channelID;
    @property(nonatomic, assign) float duration; //in seconds
    @property(nonatomic, assign) unsigned int programID;
    @property(nonatomic, strong) NSString* shortTitle;
    @end

EPGData.m

- (NSArray *)keysForEncoding;
{
    return [NSArray arrayWithObjects:@"airtime", @"channelID", @"duration", @"programID", @"shortTitle", nil];
}
Freckle answered 12/12, 2014 at 9:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.