Custom objective-c class doesn't encode - objective-c

I have a custom class that conforms to the NSCoding protocol but still refuses to encode when I call write to file.
#implementation PXLevel
- (id)initWithName:(NSString *)name andArray:(NSMutableArray *)map {
self = [super init];
self.name = name;
self.map = map;
return self;
}
- (id)initWithName:(NSString *)name {
self = [super init];
self.name = name;
NSMutableArray *tempArray = [[NSMutableArray alloc] init];
for (int i = 0; i < 8; i++) {
NSMutableArray *line = [[NSMutableArray alloc] init];
for (int j = 0; j < 8; j++) {
[line addObject:#"grass"];
}
[tempArray addObject:line];
}
self.map = tempArray;
return self;
}
-(void) encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.name forKey:#"levelName"];
[aCoder encodeObject:self.map forKey:#"levelMap"];
}
-(id) initWithCoder:(NSCoder *)aDecoder
{
if((self = [super init]))
{
self.name = [aDecoder decodeObjectForKey:#"levelName"];
self.map = [[aDecoder decodeObjectForKey:#"levelMap"] mutableCopy];
}
return self;
}
As you can see, variable map is a multidimensional array with strings in it. Not sure if this is messing it up or something.
My interface btw
#interface PXLevel : NSObject <NSCoding>
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSMutableArray *map;
- (id)initWithName:(NSString *)name andArray:(NSMutableArray *)map;
- (id)initWithName:(NSString *)name;

Assuming you are trying to write to file using writeToFile:atomically:
NSCoding creates a serialised archive which cannot be used with writeToFile:atomically:. You need to use NSKeyedArchiver like following:
[NSKeyedArchiver archiveRootObject:levelObject toFile:#"path/level-object.txt"];

Related

cocoa - unarchivedObjectOfClass does not call initWithCoder

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;

NSLOG does not work

I try to get info for the array in the console but the NSLOg does not show anything. This is a class where i store data for the app. Here is the code.
#import <Foundation/Foundation.h>
#import "DAObject.h"
#interface DataModel : NSObject
#property (strong, nonatomic) NSArray *array;
#property (strong, nonatomic) NSArray *array2;
#end
and in the .m file
#import "DataModel.h"
#implementation DataModel
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
}
return self;
}
- (NSArray *) _array
{
DAObject *obj1 = [[DACityObject alloc] init];
obj1.name = #"Obj1";
DACityObject *obj2 = [[DACityObject alloc] init];
obj2.name = #"Obj2";
_array = #[ obj1, obj2];
for (int i = 0; i < [_array count]; i++)
{
NSLog(#"Item %d = %#", i, [_array objectAtIndex:i]);
}
return _array;
}
- (NSArray *) array2 {
_array2 = [[NSArray alloc] initWithObjects:#"icon1.png", #"icon2.png",#"icon3.png", nil];
for (int i = 0; i < [_array2 count]; i++) {
NSLog(#"Item %d = %#", i, [_array2 objectAtIndex:i]);
}
return _array2;
}
#end
Where is the problem? I can't figure it out.
You need to access from anywhere to that getters, after that you will see logs.
For example try to do that in your AppDelegate.m
#import "DataModel.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
DataModel *data = [DataModel new];
data.array;
data.array2;
}

Error when using method "no Known class method for selector"

The class "PlayingCardDeck" inherits "Deck" class can not put the method
- (void) addCard:(Card *)card atTop:(BOOL)atTop
Xcode accuses me the following error:
no Known class method for selector 'addCard:atTop'
Class: PlayingCardDeck
#import "PlayingCardDeck.h"
#import "PlayingCard.h"
#implementation PlayingCardDeck
- (id)init
{
self = [super init];
if(self) {
for(NSString *suit in [PlayingCard validSuits]){
for (NSUInteger rank = 1; rank <= [PlayingCard maxRank]; rank++){
PlayingCard *card = [[PlayingCard alloc] init];
card.rank = rank;
card.suit = suit;
[PlayingCardDeck addCard:card atTop:NO];
}
}
}
return self;
}
#end
Class: Deck
#import "Deck.h"
#interface Deck()
#property (strong, nonatomic) NSMutableArray *cards;
#end
#implementation Deck
- (NSMutableArray *)cards
{
if(!_cards) _cards = [[NSMutableArray alloc] init];
return _cards;
}
- (void) addCard:(Card *)card atTop:(BOOL)atTop
{
if(atTop){
[self.cards insertObject:card atIndex:0];
} else {
[self.cards addObject:card];
}
}
- (Card *)drawRandomCard
{
Card *randomCard = nil;
if(self.cards.count){
unsigned index = arc4random() % self.cards.count;
randomCard = self.cards[index];
[self.cards removeObjectAtIndex:index];
}
return randomCard;
}
#end
thank you all for the help.
The method -addCard:atTop: is an instance method. It must be sent to an instance of a class, not the class itself. This line:
[PlayingCardDeck addCard:card atTop:NO];
is attempting to send it to the class PlayingCardDeck, not any instance of that class. That's what the error is trying to tell you.
Since that code is in the -init method of the PlayingCardDeck class, you probably meant to send the message to self:
[self addCard:card atTop:NO];

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.

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.