cocoa - unarchivedObjectOfClass does not call initWithCoder - objective-c

I am developing a macOS app using objective-C.
I tried to save a NSArray object in Core Data. I write
- (id)reverseTransformedValue:(id)value
{
return [NSKeyedUnarchiver unarchivedObjectOfClass:[NSArray class] fromData:value error:nil];
}
in the class that inherits NSValueTransformer.
And one of elements in my NSArray object is not of primary kind(which has properties called courseName and courseInfos), so I conform in this element's class. In this class ,I write:
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [[self class] new];
if (self = [super init])
{
self.courseName = [aDecoder decodeObjectForKey:#"courseName"];
self.courseInfos = [aDecoder decodeObjectForKey:#"courseInfos"];
}
return self;
}
When my apps runs, the reverseTransformedValue: method is called, and all elements but that special nonprimary element in my NSArray object are decoded. I put a breakpoint in the initWithCoder: method in that special element's class, and it never runs. I use some tools and find that element is successfully stored in my core data, so its encoding process has no problem.

I Fixed it, here is the solution:
My object is 'Address' (note: it is not NSArray)
Address.h
#interface Address : NSObject <NSSecureCoding>
#property (nonatomic, strong) NSString *street;
#property (nonatomic, strong) NSString *city;
#end
Address.m
#implementation Address
- (id) initWithCoder:(NSCoder *)aDecoder
{
self = [self init];
if (self == nil)
{
return nil;
}
self.street = [aDecoder decodeObjectOfClass:[Address class] forKey:#"street"];
self.city = [aDecoder decodeObjectOfClass:[Address class]
forKey:#"city"];
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:self.street forKey:#"street"];
[encoder encodeObject:self.city forKey:#"city"];
}
+ (BOOL)supportsSecureCoding
{
return YES;
}
Archive.h
#property (nonatomic, strong) Address *address;
Archive.m
UserDBO *userDBO = [[UserDBO alloc] init];
// Archive
NSError *error = nil;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.address requiringSecureCoding:YES error:&error];
if(error)
{
NSLog(#"archivedDataWithRootObject: %#", error);
}
userDBO.address = data;
// UnArchive
Address *address = [NSKeyedUnarchiver unarchivedObjectOfClass:[Address class] fromData:userDBO.address error:&error];
if(error)
{
NSLog(#"unarchivedObjectOfClass: %#", error);
}
UserDBO.h
#property (nonatomic, strong) NSData *address;

Related

Map coordinates not decoding

In my app, I am trying to save the pins that are on the map so that they are there when the user opens the app after it is terminated. I have conformed my mkAnnotation class to NSCoding, and implemented the two required methods. The annotations are all stored in a NSMutableArray in a singleton class, so I am really just trying to save the array in the singleton class. Everything is being encoded fine, but I do not think they are being decoded. Here is some code:
This is my MKAnnotation class:
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>
#interface MapPoint : NSObject <MKAnnotation, NSCoding>
{
}
- (id)initWithAddress:(NSString*)address
coordinate:(CLLocationCoordinate2D)coordinate
title:(NSString *)t;
#property (nonatomic, readwrite) CLLocationCoordinate2D coordinate;
//This is an optional property from MKAnnotataion
#property (nonatomic, copy) NSString *title;
#property (nonatomic, readonly, copy) NSString *subtitle;
#property (nonatomic) BOOL animatesDrop;
#property (nonatomic) BOOL canShowCallout;
#property (copy) NSString *address;
#property (nonatomic, copy) NSString *imageKey;
#property (nonatomic, copy) UIImage *image;
#end
#implementation MapPoint
#synthesize title, subtitle, animatesDrop, canShowCallout, imageKey, image;
#synthesize address = _address, coordinate = _coordinate;
-(id)initWithAddress:(NSString *)address
coordinate:(CLLocationCoordinate2D)coordinate
title:(NSString *)t {
self = [super init];
if (self) {
_address = [address copy];
_coordinate = coordinate;
[self setTitle:t];
NSDate *theDate = [NSDate date];
subtitle = [NSDateFormatter localizedStringFromDate:theDate
dateStyle:NSDateFormatterShortStyle
timeStyle:NSDateFormatterShortStyle];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_address forKey:#"address"];
NSLog(#"ENCODING coordLatitude %f coordLongitude %f ", _coordinate.latitude, _coordinate.longitude);
[aCoder encodeDouble:_coordinate.longitude forKey:#"coordinate.longitude"];
[aCoder encodeDouble:_coordinate.latitude forKey:#"coordinate.latitude"];
[aCoder encodeObject:title forKey:#"title"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
[self setAddress:[aDecoder decodeObjectForKey:#"address"]];
NSLog(#"DECODING coordLatitude %f coordLongitude %f ", _coordinate.latitude, _coordinate.longitude);
_coordinate.longitude = [aDecoder decodeDoubleForKey:#"coordinate.longitude"];
_coordinate.latitude = [aDecoder decodeDoubleForKey:#"coordinate.latitude"];
[self setTitle:[aDecoder decodeObjectForKey:#"title"]];
}
return self;
}
#end
Here is my singleton class:
#import <Foundation/Foundation.h>
#class MapPoint;
#interface Data : NSObject
{
NSMutableArray *_annotations;
}
#property (retain, nonatomic) NSMutableArray *annotations;
+ (Data *)singleton;
- (NSString *)pinArchivePath;
- (BOOL)saveChanges;
#end
#implementation Data
#synthesize annotations = _annotations;
+ (Data *)singleton {
static dispatch_once_t pred;
static Data *shared = nil;
dispatch_once(&pred, ^{
shared = [[Data alloc] init];
shared.annotations = [[NSMutableArray alloc]init];
});
return shared;
}
- (id)init {
self = [super init];
if (self) {
NSString *path = [self pinArchivePath];
_annotations = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if (!_annotations) {
_annotations = [[NSMutableArray alloc]init];
}
}
return self;
}
- (NSString *)pinArchivePath {
NSArray *cachesDirectories = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachesDirectory = [cachesDirectories objectAtIndex:0];
return [cachesDirectory stringByAppendingPathComponent:#"pins.archive"];
}
- (BOOL)saveChanges {
NSString *path = [self pinArchivePath];
return [NSKeyedArchiver archiveRootObject:[Data singleton].annotations
toFile:path];
}
#end
In my viewDidLoad method on the map view controller, I try and place the annotations in the singleton array on the map with this:
for (MapPoint *mp in [Data singleton].annotations) {
[_worldView addAnnotation:mp];
}
The main problem is in the singleton method in these lines:
dispatch_once(&pred, ^{
shared = [[Data alloc] init];
shared.annotations = [[NSMutableArray alloc]init]; //<-- problem line
});
The shared = [[Data alloc] init]; line decodes and initializes the annotations array.
Then the shared.annotations = [[NSMutableArray alloc]init]; line re-creates and re-initializes the annotations array thus discarding the just-decoded annotations so the singleton always returns an empty array.
Remove the shared.annotations = [[NSMutableArray alloc]init]; line.
As already mentioned in the comment, the other minor issue, which causes simply confusion, is the placement of the NSLog where the coordinate is being decoded. The NSLog should be after the decode is done.

NSKeyedArchiver archivedDataWithRootObject: does not call encodeWithCoder

I cant get the archivedDataWithRootObject method to call encodeWithCoder, it seems pretty simple from what I understand but it doesn't work.
Here's the code:
#import <Foundation/Foundation.h>
#interface Details : NSObject <NSCoding>{
NSMutableArray *myArray;
}
#property(nonatomic,retain) NSMutableArray *myArray;
#end
#import "Details.h"
#implementation Details
#synthesize myArray;
-(id)init{
[super init];
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
NSLog(#"initWithCoder");
if (self = [super init])
{
self.myArray = [[aDecoder decodeObjectForKey:#"details"]retain];
}
return self;
}
- (NSMutableArray*)getDetails{
NSLog(#"getDetials");
// NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *details = [[NSUserDefaults standardUserDefaults] objectForKey:#"details"];
if (details != nil){
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:details];
if (oldSavedArray != nil)
self.myArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
else
self.myArray = [[NSMutableArray alloc] initWithCapacity:1];
}
return self.myArray;
}
- (void) addDetails:(NSMutableArray *)details{
NSLog(#"add Details");
[self.myArray addObject:details];
[NSKeyedArchiver archivedDataWithRootObject:self.myArray];
}
- (void) encodeWithCoder:(NSCoder *)coder;
{
NSLog(#"encodeWithCoder");
[coder encodeObject:self.myArray forKey:#"details"];
}
#end
NSLog(#"encodeWithCoder") doesn't run.
I might be missing something, If anyone has any ideas it would be appreciated.
[NSKeyedArchiver archivedDataWithRootObject:self.myArray];
You are telling the NSKeyedArchiver to archive the myArray variable, not the object you have just implemented <NSCoding> with!
Also that method returns NSData * which you then have to write to disk separately if needed.
Try this:
NSData * archivedData = [NSKeyedArchiver archivedDataWithRootObject:self];

Init a NSMutableArray in a singelton that saves as a state

Im am using a pattern from the book Learning Cocos2D. I have a GameState class that is a singleton that can be saved as a state. The BOOL soundOn gets init as false the first time the class gest created, but the array levelProgress dosent. I made comments on all the lines on which I have tried to init the array.
The class uses a helper that loads and saves the data.
When i try 1 or 2 i get "instance variable 'levelProgress' accessed in class method" error.
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface GameState : NSObject <NSCoding> {
BOOL soundOn;
NSMutableArray *levelProgress;
}
+ (GameState *) sharedInstance;
- (void)save;
#property (assign) BOOL soundOn;
#property (nonatomic, retain) NSMutableArray *levelProgress;
#end
#import "GameState.h"
#import "GCDatabase.h"
#implementation GameState
#synthesize soundOn;
#synthesize levelProgress;
static GameState *sharedInstance = nil;
+(GameState*)sharedInstance {
#synchronized([GameState class])
{
if(!sharedInstance) {
sharedInstance = [loadData(#"GameState") retain];
if (!sharedInstance) {
[[self alloc] init];
// 1. levelProgress = [[NSMutableArray alloc] init];
}
}
return sharedInstance;
}
return nil;
}
+(id)alloc
{
#synchronized ([GameState class])
{
NSAssert(sharedInstance == nil, #"Attempted to allocate a \
second instance of the GameState singleton");
sharedInstance = [super alloc];
// 2. levelProgress = [[NSMutableArray alloc] init];
return sharedInstance;
}
return nil;
}
- (void)save {
saveData(self, #"GameState");
}
- (void)encodeWithCoder:(NSCoder *)encoder {
// 3. levelProgress = [[NSMutableArray alloc] init];
[encoder encodeBool:currentChoosenCountry forKey:#"soundOn"];
[encoder encodeObject:levelProgress forKey:#"levelProgress"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super init])) {
// 4. levelProgress = [[NSMutableArray alloc] init];
soundOn = [decoder decodeBoolForKey:#"soundOn"];
levelProgress = [decoder decodeObjectForKey:#"levelProgress"];
}
return self;
}
#end
Solution:
*I just added av init method...*
-(id)init {
self = [super init];
if (self != nil) {
levelProgress = [[NSMutableArray alloc] init];
}
return self;
}
Try this (this code may contain syntax or logic errors - I wrote it in notepad):
#interface GameState : NSObject <NSCoding>
#property (nonatomic, readwrite) BOOL soundOn;
#property (nonatomic, retain) NSMutableArray *levelProgress;
+ (GameState *)sharedState;
- (void)writeDataToCache;
#end
//
#implementation GameState
#synthesize soundOn, levelProgress;
#pragma mark - Singleton
static GameState *sharedState = nil;
+ (void)initialize {
static BOOL initialized = NO;
if (!initialized) {
initialized = YES;
if ([[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:#"%#/gameState", pathCache]]) {
NSData *encodedObject = [[NSData alloc] initWithContentsOfFile:[NSString stringWithFormat:#"%#/gameState", pathCache]];
data = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
}
else
data = [[GameState alloc] init];
}
}
+ (GameState *)sharedState {
return sharedState;
}
#pragma mark - Initialization
- (id)init {
if (self = [super init]) { // will be inited while application first run
soundOn = NO;
levelProgress = [[NSMutableArray alloc] init];
return self;
}
return nil;
}
#pragma mark - Coding Implementation
- (void)writeDataToCache { // use this method to save current state to cache
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:self];
if([[NSFileManager defaultManager] fileExistsAtPath:[NSString stringWithFormat:#"%#/GameState", pathCache]])
[[NSFileManager defaultManager] removeItemAtPath:[NSString stringWithFormat:#"%#/GameState", pathCache] error:nil];
[[NSFileManager defaultManager] createFileAtPath:[NSString stringWithFormat:#"%#/GameState", pathCache] contents:encodedObject attributes:nil];
NSLog(#"GameState was saved successfully.");
}
- (void)encodeWithCoder:(NSCoder*)encoder {
[encoder encodeBool:self.soundOn forKey:#"soundOn"];
[encoder encodeObject:self.levelProgress forKey:#"levelProgress"];
}
- (id)initWithCoder:(NSCoder*)decoder {
if ((self = [super init])) {
self.soundOn = [decoder decodeBoolForKey:#"soundOn"];
self.levelProgress = [decoder decodeObjectForKey:#"levelProgress"];
NSLog(#"GameState was inited successfully");
return self;
}
return nil;
}
#end
1 & 2 are the same (and should not work, since levelProgress is an instance variable), 3 and 4 should be encoding/decoding the array, not initializing it anew.
1 and 2 are a mistake - you can't access instance variable without specifying an instance.
You can fix 1 by changing the line to sharedInstance.levelProgress = ....
2 is just a bad idea, you're breaking common convention of initializing using init... methods, it may surprise and cause problems for an other programmer who'll be working on the same code later.
3 and 4 are ok but if loadData fails they will not be executed and the object will be initialized with ordinary init and the array will be nil pointer:
[[self alloc] init];
you should also override init and initialize the properties there.

MKAnnotationView's subtitle not getting reloaded

I currently have an XML model getting processed my an NSXMLParser; I get about 340 objects in my model after processing. I place all of the objects on my MKMapView. Once a user "selects" an object (MKAnnotationViewPin); once I start off, the title is populated, coords too (duh?) but the subtitle is set to a place-holder. I process another XML file to retrieve extra information, the subtitle gets updated and populated.
Once the XML file gets parsed I get notified and update its <MKAnnotation> object to reflect the changed subtitle. To reflect the change on the map I have to "unselect" the pin and click it again for it to show the change.
Here is my <MKAnnotation> object:
Header:
#interface CPCustomMapPin : NSObject <MKAnnotation>
{
NSString *_title;
NSString *_annotation;
CLLocationCoordinate2D _coords;
NSString *stationID;
}
#property (nonatomic, retain) NSString *_title;
#property (nonatomic, retain) NSString *_annotation;
#property (nonatomic) CLLocationCoordinate2D _coords;
#property (nonatomic, retain) NSString *stationID;
- (id) initWithTitle: (NSString *) _title withAnnotation: (NSString *) _annotation withCoords: (CLLocationCoordinate2D) _coords withStationID: (NSString *) _id;
Implementation:
#implementation CPCustomMapPin
#synthesize _title, _annotation, _coords, stationID;
- (id) initWithTitle: (NSString *) __title withAnnotation: (NSString *) __annotation withCoords: (CLLocationCoordinate2D) __coords withStationID: (NSString *) _id
{
_title = [[NSString alloc] init];
_annotation = [[NSString alloc] init];
stationID = [[NSString alloc] init];
[self set_title: __title];
[self set_annotation: __annotation];
[self set_coords: __coords];
[self setStationID: _id];
return self;
}
- (NSString *) title
{
return _title;
}
- (NSString *) subtitle
{
return _annotation;
}
- (CLLocationCoordinate2D) coordinate
{
return _coords;
}
- (NSString *) description
{
return [NSString stringWithFormat: #"title: %# subtitle: %# id: %#", _title, _annotation, stationID];
}
- (void) dealloc
{
[_title release];
[_annotation release];
[stationID release];
[super dealloc];
}
#end
Thank you for your valuable input.
Apparently no one knows… I just found this technique:
- (void) closeAnnotation: (id <MKAnnotation>) annotation inMapView: (MKMapView *) mapView
{
[mapView deselectAnnotation: annotation animated: NO];
[mapView selectAnnotation: annotation animated: YES];
}
And of course you call your method accordingly:
Example:
- (void) myMethod
{
for (id <MKAnnotation> _annotation in mapView.annotations)
{
[self closeAnnotation: _annotation inMapView: mapView];
}
}

Why does this crash?

I made a class. This is the h file.
// MyClass.h
#import <Foundation/Foundation.h>
#interface MyClass : NSObject <NSCoding> {
NSString *string1;
NSString *string2;
}
#property (nonatomic, retain) NSString *string1;
#property (nonatomic, retain) NSString *string2;
#end
This is the m file.
// MyClass.m
#import "MyClass.h"
#implementation MyClass
#synthesize string1, string2;
- (void)encodeWithCoder:(NSCoder *)coder;
{
if (self = [super init]){
[coder encodeObject:string1 forKey:#"string1"];
[coder encodeObject:string2 forKey:#"string2"];
}
}
- (id)initWithCoder:(NSCoder *)coder;
{
self = [[MyClass alloc] init];
if (self != nil)
{
string1 = [coder decodeObjectForKey:#"string1"];
string2 = [coder decodeObjectForKey:#"string2"];
}
return self;
}
- (void)viewDidUnload {
self.string1 = nil;
self.string2 = nil;
}
- (void)dealloc {
[super dealloc];
[string1 release];
[string2 release];
}
#end
I created an array of these objects like this:
MyClass *object1 = [[MyClass alloc] init];
object1.string1 = #"object1 string1";
object1.string2 = #"string1 string2";
MyClass *object2 = [[MyClass alloc] init];
object2.string1 = #"object2 string1";
object2.string2 = #"object2 string2";
theArray = [[NSMutableArray alloc] initWithObjects:object1, object2, nil];
Then I saved the array like this:
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:theArray] forKey:#"savedArray"];
Then I loaded the array from disk like this.
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:#"savedArray"];
if (dataRepresentingSavedArray != nil)
{
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if (oldSavedArray != nil)
{
theArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
}
else {
theArray = [[NSMutableArray alloc] init];
}
}
The program crashes when it gets to this line in - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
cell.textLabel.text = [[theArray objectAtIndex:indexPath.row] string1];
Why does it crash there? It doesn't crash if I don't load the array from NSUserDefaults. However, I don't see anything I did wrong with saving or loading the array.
Edit:
I can also make it crash with this line of code:
NSLog(#"%#", [[theArray objectAtIndex:0] string1]) ;
In addition to cobbal's excellent points, your initWithCoder: method isn't using setters and therefore the strings aren't being retained. When you try to access string1 at your crash line, that string has probably already been released. Do this in initWithCoder: instead:
self.string1 = [coder decodeObjectForKey:#"string1"];
self.string2 = [coder decodeObjectForKey:#"string2"];
To address cobbal's first point, don't do any init at all in encodeWithCoder:. Just encode your object. Check out Apple's Archives and Serializations Programming Guide for Cocoa for more details about encoding and decoding objects.
A few things jump out at me
you're calling init in encodeWithCoder:. since you're not initializing anything here you should not be changing self
you're calling alloc in initWithCoder:. Once you're in an init method you shouldn't have to call alloc, just call self = [super init].
you have a viewDidUnload method. Even if this were called in an NSObject subclass, you probably don't want to get rid of your data when the view is unloaded.
you're calling [super dealloc] at the beginning of your dealloc method. It should go at the end.
I didn't read all of your code, so I am not sure if this is the problem, but in dealloc you should call [super dealoc] only after releasing the instance variables.
If it crashes due to being out of bounds, check that your UITableViewController correctly implements
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
and
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
to reflect the NSArray you're using as data source.