Search code examples
iosobjective-cnsobjectnskeyedarchivernskeyedunarchiver

Reading wrong class type with NSKeyedUnarchiver


In my app, I'm reading and writing an NSMutableArray to NSUserDefaults. This array contains objects that look like this:

header file:

#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>

@interface WorkLocationModel : NSObject

@property (nonatomic, strong) CLRegion *geoLocation;

@end

implementation file:

#import "WorkLocationModel.h"

@implementation WorkLocationModel

-(id)init {
// Init self
self = [super init];
if (self)
{
    // Setup
}
return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.geoLocation forKey:@"geoLocation"];
}

-(void)initWithCoder:(NSCoder *)coder {
self.geoLocation = [coder decodeObjectForKey:@"geoLocation"];
}

@end

This is how I read my list:

This is in the ViewController where I load the array, oldArray seems to log 1 item (correct amount) of the type NSKeyedUnarchiver when it's supposed to be a WorkLocationModel object:

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *workLocationsData = [defaults objectForKey:@"workLocations"];
if (workLocationsData != nil)
{
    NSArray *oldArray = [NSKeyedUnarchiver unarchiveObjectWithData:workLocationsData];

    if (oldArray != nil)
    {
        _workLocations = [[NSMutableArray alloc] initWithArray:oldArray];
        NSLog(@"Not nil, count: %lu", (unsigned long)_workLocations.count);
    } else
    {
        _workLocations = [[NSMutableArray alloc] init];
    }
} else
{
    _workLocations = [[NSMutableArray alloc] init];
}

This is how I add the WorkLocationModel objects to my array:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// Create a sample work location
WorkLocationModel *newModel = [[WorkLocationModel alloc] init];
newModel.geoLocation = currentRegion;
[_workLocations addObject:newModel];

// Save the new objects
[defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:_workLocations] forKey:@"workLocations"];

// Synchronize the defaults
[defaults synchronize];

The error occurs on the if-statement here (further down into my ViewController), where I'm comparing two CLRegions.

region is a function argument.

    for (WorkLocationModel *currentWorkLocationModel in _workLocations)
{
    if ([region isEqual:currentWorkLocationModel.geoLocation])
    {
        // Found a match
    }
}

I've gone through the code but I don't understand why this is happening, exception message:

-[NSKeyedUnarchiver geoLocation]: unrecognized selector sent to instance 0x174108430
2015-01-12 18:23:20.085 myApp[1322:224462] *** Terminating app due to
uncaught exception 'NSInvalidArgumentException', reason: '-[NSKeyedUnarchiver geoLocation]:
unrecognized selector sent to instance

Can someone help me with this? I'm lost


Solution

  • The initWithCoder: method is just like any other init method - it must call super and return an instance. Your method should be:

    - (instancetype) initWithCoder:(NSCoder *)coder
    {
       self = [super init];
       if(self)
          self.geoLocation = [coder decodeObjectForKey:@"geoLocation"];
       return self;
    }
    

    If your superclass implements initWithCoder: as well the super call would be [super initWithCoder:coder] (and similarly for encodeWithCoder:) - NSObject does not so you are OK in both your encoding and decoding.

    A breakpoint on your encode, decode and reading methods would have quickly narrowed your problem down for you.

    HTH