Custom NSObject iniWithCoder not called - ios

I have a custom object, LevelContent, which contains some properties. LevelContentconforms to NSCoding, and I have implemented encodeWithCoder: and initWithCoder: methods. I save and fetch the data to Parse.com. Saving works fine like this:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.currentLevelContent];
When I fetch the data, I get the NSDatacorrectly, but when I try to initialize the LevelContentwith the downloaded data, initWithCoder: never gets called. I try to load the LevelContent with this:
LevelContent *content = [NSKeyedUnarchiver unarchiveObjectWithData:data];
Here is the code for encoding/decoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.tiles forKey:#"tiles"];
[aCoder encodeObject:self.attributes forKey:#"attributes"];
[aCoder encodeObject:self.level forKey:#"level"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self == [super init]) {
self.tiles = [NSMutableArray array];
[self.tiles addObjectsFromArray:[aDecoder decodeObjectForKey:#"tiles"]];
self.attributes = [NSMutableDictionary dictionary];
[self.attributes addEntriesFromDictionary:[aDecoder decodeObjectForKey:#"attributes"]];
self.level = [Level levelWithTopIndex:0 detailIndex:0];
self.level = [aDecoder decodeObjectForKey:#"level"];
}
return self;
}

Change if (self == [super init]) to if (self = [super init]) in initWithCoder:
Try this,
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
.....
}
return self;
}

Related

Using NSKeyedArchiver throw Exception:[NSConcreteValue length]: unrecognized selector sent to instance

I tried to use NSKeyedArchiver and NSKeyedUnarchiver.The code throw exception: "[NSConcreteValue length]: unrecognized selector sent to instance".
#interface DBTLine() <NSCoding>
#end
#implementation DBTLine
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeCGPoint:self.begin forKey:#"begin"];
[aCoder encodeCGPoint:self.end forKey:#"end"];
[aCoder encodeObject:self.color forKey:#"color"];
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super init];
if (self) {
//Throw Exception Here.
_begin = [coder decodeCGPointForKey:#"begin"];
_end = [coder decodeCGPointForKey:#"end"];
_color = [coder decodeObjectForKey:#"color"];
}
return self;
}
#end
#interface DBTDrawView()
#property (nonatomic)NSMutableArray *finishedLines;
#end
#implementation DBTDrawView
-(instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if(self){
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData* dataFinishedLines = [defaults objectForKey:#"finishedLines"];
NSMutableArray *defaultLines = [NSKeyedUnarchiver unarchiveObjectWithData:dataFinishedLines];
if (defaultLines) {
self.finishedLines = defaultLines;
}
}
return self;
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//some code,"self.finishedLines" add line
NSData *dataFinishedLines = [NSKeyedArchiver archivedDataWithRootObject:self.finishedLines];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:dataFinishedLines forKey:#"finishedLines"];
[self setNeedsDisplay];
}
#end
I searched the Internet for a long time,and I've tried multiple variations of my code.Finally,It works.Here is my changed code:
-(void)encodeWithCoder:(NSCoder *)aCoder {
NSValue *begin = [NSValue valueWithCGPoint:self.begin];
NSValue *end = [NSValue valueWithCGPoint:self.end];
[aCoder encodeObject:begin forKey:#"begin"];
[aCoder encodeObject:end forKey:#"end"];
[aCoder encodeObject:self.color forKey:#"color"];
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super init];
if (self) {
NSValue *begin = [coder decodeObjectForKey:#"begin"];
NSValue *end = [coder decodeObjectForKey:#"end"];
_begin = [begin CGPointValue];
_end = [end CGPointValue];
_color = [coder decodeObjectForKey:#"color"];
}
return self;
}
But the problem is:
It seems the problem appeared in the process of using "-
encodeCGPoint:forKey: - decodeCGPointForKey:".Was there anything
wrong with what I did?
What is "[NSConcreteValue length]"?I can't find it in the document.
Thanks in advance.

Trying to initialize array objective -c

So I know my problem is that my transfer array is not being initialized correctly. Where should I put transfer = [[NSMutableArray alloc] init];?
#interface PictureViewController (){
Poi *labelPoi;
}
#end
#implementation PictureViewController
#synthesize imageX;
#synthesize imageY;
#synthesize name;
#synthesize transfer;
- (id)init
{
self = [super init];
if(self) {
transfer = [[NSMutableArray alloc] init];
}
return self;
}
-(id)initWithLabel:(double)imageX andY:(double)imageY withName:(NSString *)name{
self = [super init];
if(self){
// transfer = [[NSMutableArray alloc] init];
self.imageX = imageX;
self.imageY = imageY;
self.name = name;
}
return self;
}
/*-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if(self){
transfer = [[NSMutableArray alloc] init];
self.imageX = imageX;
self.imageY = imageY;
self.name = name;
NSLog(#"imageX: %f", self.imageX);
NSLog(#"imageY: %f", imageY);
NSLog(#"name: %#", name);
}
return self;
}*/
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSLog(#"transfer count: %lu",(unsigned long)transfer.count);
for(int i = 0; i < transfer.count; i++){
UILabel *label = [[UILabel alloc] initWithFrame: CGRectMake([[transfer objectAtIndex:i] imageLocationX], [[transfer objectAtIndex:i] imageLocationY], 200, 50)];
label.text = [[transfer objectAtIndex:i] name];
label.font = [UIFont systemFontOfSize:14];
label.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.0];
[self.view addSubview:label];
NSLog(#"asdfasdsd");
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (id)display:(double)imageXX andY:(double)imageYY withName:(NSString *)namee{
NSLog(#"%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%");
NSLog(#"imageX: %f",imageXX);
NSLog(#"imageY: %f", imageYY);
NSLog(#"name: %#", namee);
labelPoi = [[Poi alloc] init];
//transfer = [[NSMutableArray alloc] init];
labelPoi.imageLocationX = imageXX;
labelPoi.imageLocationY = imageYY;
labelPoi.name = namee;
[transfer addObject:labelPoi];
NSLog(#"label.x: %f should be: %f", labelPoi.imageLocationX, imageXX);
NSLog(#"label.y: %f should be: %f", labelPoi.imageLocationY, imageYY);
NSLog(#"label.name: %# should be: %#",labelPoi.name,namee);
NSLog(#"transssssfer: %lu", (unsigned long)transfer.count);
NSLog(#"asfddfsaasfdfdsfsd %f", [[transfer objectAtIndex:0] imageLocationX]);
return self;
}
#end
The Poi object is made up of an imageLocationX, imageLocationY, and name and I am trying to put the Poi object into an array named transfer however, whenever I try to access transfer elements, I receive 0 or null. The (id)display function is being called several times from a different view NSMutable alloc in that function, the array gets reset.
Here is the output:
2013-07-19 11:22:06.736 AR_UAV_App[12466:11303] %%%%%%%%%%%%%%%%%%%%%%%%
2013-07-19 11:22:06.736 AR_UAV_App[12466:11303] imageX: 224.485718
2013-07-19 11:22:06.736 AR_UAV_App[12466:11303] imageY: 116.353401
2013-07-19 11:22:06.736 AR_UAV_App[12466:11303] name: Beutel Student Health Center
2013-07-19 11:22:06.736 AR_UAV_App[12466:11303] label.x: 224.485718 should be: 224.485718
2013-07-19 11:22:06.736 AR_UAV_App[12466:11303] label.y: 116.353401 should be: 116.353401
2013-07-19 11:22:06.736 AR_UAV_App[12466:11303] label.name: Beutel Student Health Center should be: Beutel Student Health Center
2013-07-19 11:22:06.737 AR_UAV_App[12466:11303] transssssfer: 0
2013-07-19 11:22:06.737 AR_UAV_App[12466:11303] asfddfsaasfdfdsfsd 0.000000
2013-07-19 11:22:06.737 AR_UAV_App[12466:11303] #############################################################
EDIT: .h file
#interface PictureViewController : UIViewController{
NSMutableArray *transfer;
}
#property (nonatomic) double imageX;
#property (nonatomic) double imageY;
#property (nonatomic) NSString *name;
#property (nonatomic,retain) NSArray *transfer;
- (IBAction)backView:(id)sender;
- (IBAction)load:(id)sender X:(double)imageX andY:(double)imageY withName:(NSString *)name;
-(id)initWithLabel:(double)imageX andY:(double)imageY withName:(NSString *)name;
-(id)display:(double)imageX andY:(double)imageY withName:(NSString *)name;
#end
This init method should become the "designated" initializer:
-(id)initWithLabel:andY:withName:(NSString *)name
(by the way, it's not named correctly according the naming conventions)
The designated initializer shall initialize the instance properly (that is, in your case it may initialize the array transfer, unless you use a lazy accessor ).
The "designated initializer" is most often the "most specialized" initializer - that is, that one with the most parameters. Most often, there is only one and easily identifiable designated initializer.
The "designated initializer" has the canonical form:
-(id)initWithLabel:(double)imageX andY:(double)imageY withName:(NSString *)name{
self = [super init];
if(self){
// initialization
...
}
}
Other init methods like the init method shall invoke the designated initializer like:
- (id)init {
return [self initWithLabel:0 andY:0 withName:#""];
}
I guess one problem can be that when you are creating this controller you are calling to:
-(id)initWithLabel:(double)imageX andY:(double)imageY withName:(NSString *)name
In case you do that, you are calling to [super init] what will never go through the init method of your class (where you allocated the array). In case you have different methods to init a controller I recommend you to have a:
commonInit{
_transfer = [[NSMutableArray] alloc] init];
}
Then call this method for every init method that you have in your controller so you ensure that array is allocated.
Other thing is just allocate your array in the viewDidLoad of your controller.
Just for making know, you can "allocated" an array without having to take care about release the object by calling to [NSMutableArray arrayWithCapacity:0];
Are you initializing your PictureViewController with -(id)initWithLabel:(double)imageX andY:(double)imageY withName:(NSString *)name? If so, I can tell you what the problem is...
-(id)initWithLabel:(double)imageX andY:(double)imageY withName:(NSString *)name
{
self = [super init]; // <--- You are likely confused about this line
.....
}
[super init] does not pass a message to your custom -(id)init method. It instead is referring to PictureViewController's superclass, which is likely UIViewController.
Your problem should be fixed by uncommenting transfer = [[NSMutableArray alloc] init]; in the initWithLabel: custom init method.
make a property (synthesize it #synthesize transfer=_transfer;), use lazy instantiation like:
-(NSMutableArray*)transfer
{
if (!_transfer)
{
_transfer=[NSMutableArray array];
}
return _transfer;
}
and take it out of your init

NSCoder Not Returning Values correctly

In the following code, the properties are never set correctly in the sharedInstance method, and I can't figure out why. It seems like I have the correct values though before I save it with the archiver.
#import "User.h"
static User *sharedInstance = nil;
#define NAME #"name"
#define USER_ID #"id"
#define ACCOUNT_ID #"account_id"
#define USER_NAME #"username"
#define ADMIN #"admin"
#define CURRENT_USER #"current_user"
#implementation KSUser
+ (id)sharedInstance
{
#synchronized(self) {
if (sharedInstance == nil) {
NSData *userData = [[NSUserDefaults standardUserDefaults] objectForKey:CURRENT_USER];
if (userData) {
sharedInstance = [NSKeyedUnarchiver unarchiveObjectWithData:userData];
}
else {
sharedInstance = [[super alloc] init];
}
}
}
return sharedInstance;
}
- (void)populateFromJSON:(NSDictionary *)json
{
sharedInstance.name = json[NAME];
sharedInstance.accountId = json[ACCOUNT_ID];
sharedInstance.userId = json[USER_ID];
sharedInstance.userName = json[USER_NAME];
sharedInstance.admin = [json[ADMIN] boolValue];
sharedInstance.loggedIn = YES;
NSLog(#"values are: name: %#, %#, %#, %#", sharedInstance.name, sharedInstance.accountId, sharedInstance.userId, sharedInstance.userName);
}
- (void)logout
{
sharedInstance.name = nil;
sharedInstance.accountId = nil;
sharedInstance.userId = nil;
sharedInstance.userName = nil;
sharedInstance.admin = NO;
sharedInstance.loggedIn = NO;
[self saveState];
}
- (void)saveState
{
NSLog(#"values are: name: %#, %#, %#, %#", sharedInstance.name, sharedInstance.accountId, sharedInstance.userId, sharedInstance.userName);
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:sharedInstance];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:CURRENT_USER];
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:sharedInstance.userId forKey:USER_ID];
[aCoder encodeObject:sharedInstance.accountId forKey:ACCOUNT_ID];
[aCoder encodeObject:sharedInstance.name forKey:NAME];
[aCoder encodeObject:sharedInstance.userName forKey:USER_NAME];
[aCoder encodeBool:sharedInstance.admin forKey:ADMIN];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
sharedInstance.userId = [aDecoder decodeObjectForKey:USER_ID];
sharedInstance.accountId = [aDecoder decodeObjectForKey:ACCOUNT_ID];
sharedInstance.name = [aDecoder decodeObjectForKey:NAME];
sharedInstance.userName = [aDecoder decodeObjectForKey:USER_NAME];
sharedInstance.admin = [aDecoder decodeBoolForKey:ADMIN];
}
return self;
}
#end
Any help would be greatly appreciated.
It is because your global variable sharedinstance in method - (id)initWithCoder:(NSCoder *)aDecoder is always nil
In initWithCoder: don't refer to the shared instance but rather self instead. At the time it's executing sharedInstance is nil.
Also, you only call saveState after logging out, so it only saves out the nil values.

My NSMutableArray suddenly becomes a CALayer

I think I am hallucinating. I am trying to add some persistence to my Concentration-lke game. I would like to keep track of high scores. I got this partially working for a little while today and now it has all gone kablooie (I think that is the correct iOS terminology). Now, my allHighScores NSMutablearray suddenly becomes a CALayer. I am using NSKeyed Archiving. I have a break point in my file before allHighScores gets loaded with data. When stepping through the application, allHighScores exists as an NSMutableArray - then, at the next step, it suddenly becomes a CA Layer. Huh?
-(id)init
{
self = [super init];
if (self) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
allHighScores = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if (!allHighScores) {
allHighScores = [[NSMutableArray alloc] init];
}
}
return self;
}
+(FlipHighScoreStore *)sharedStore {
static FlipHighScoreStore *sharedStore = nil;
if (!sharedStore) {
sharedStore = [[super allocWithZone:nil]init];
}
return sharedStore;
}
Somehow, calling NSKeyedUnarchiver changes my allHighScores from an NSMutableArray into a CALayer. I am very confused.
I tried adding a retain to the unarchiving instruction, but that didn't help.
Here is my encoding/decoding code:
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.themeChosen forKey:#"themeChosen"];
[aCoder encodeInt:self.highScore forKey:#"highScore"];
[aCoder encodeInt:self.scoreStartLevel forKey:#"scoreStartLevel"];
[aCoder encodeInt:self.scoreFinishLevel forKey:#"scoreFinishLevel"];
[aCoder encodeObject:scoreDateCreated forKey:#"scoreDateCreated"];}
-(id)initWithCoder:(NSCoder *)aDecoder {
if (self) {
self.themeChosen = [aDecoder decodeObjectForKey:#"themeChosen"];
self.highScore = [aDecoder decodeIntForKey:#"highScore"];
self.scoreStartLevel = [aDecoder decodeIntForKey:#"scoreStartLevel"];
self.scoreFinishLevel = [aDecoder decodeIntForKey:#"scoreFinishLevel"];
scoreDateCreated = [aDecoder decodeObjectForKey:#"scoreDateCreated"];
}
return self;}
UPDATE: The program crashes when a "highscores.archive" file already exists and a save is called again. I can launch the app, look at the high scores - they are there and retrieved happily, but the save code:
-(BOOL)saveHighScores {
NSString *path = [self flipScoreArchivePath];
return [NSKeyedArchiver archiveRootObject:allHighScores toFile:path];}
causes a EXC_BAD_ACCESS. The path is right, so somehow the allHighScores isn't.
The problem here is you aren't retaining the results of the unarchiving. According to the Basic Memory Management Rules, a method by the name of +unarchiveObjectWithFile: will return an autoreleased object. As such, since you are placing it into an ivar, you need to retain this object, or it will get deallocated out from under you.
Although in your case, since you want a mutable array, you actually need to call -mutableCopy since NSKeyedUnarchive will just give you an immutable array.
-(id)init {
if ((self = [super init])) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
allHighScores = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] mutableCopy];
if (!allHighScores) {
allHighScores = [[NSMutableArray alloc] init];
}
}
return self;
}
Your -initWithCoder: isn't calling super. You need to say
if ((self = [super initWithCoder:aDecoder])) {
Have you tried this?
-(id)init {
if ((self = [super init])) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
allHighScores = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] mutableCopy];
if (!allHighScores) {
allHighScores = [[NSMutableArray alloc] init];
}
// All-important new line....
[self setAllHighScores:allHighScores];
}
return self;
}
Edit/Update:
So, here's two versions of what I actually intended in the above example (I'm assuming here that his ivar allHighScores has a corresponding property):
-(id)init {
if ((self = [super init])) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
self.allHighScores = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] mutableCopy];
if (!self.allHighScores) {
self.allHighScores = [[NSMutableArray alloc] init];
}
}
return self;
}
This is the way I'd actually do it:
-(id)init {
if ((self = [super init])) {
NSMutableArray *arr = [[NSKeyedUnarchiver unarchiveObjectWithFile:[self flipScoreArchivePath]] mutableCopy];
if (!arr) arr = [[NSMutableArray alloc] init];
[self setAllHighScores:arr];
}
return self;
}

Saving pictures in IPhone apps

I have an app with array of objects, which i archived and unarchived
-(id)initWithCoder:(NSCoder *)aDecoder{
title = [aDecoder decodeObjectForKey:#"Title"];
image = [aDecoder decodeObjectForKey:#"Image"];
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:title forKey:#"Title"];
[aCoder encodeObject:image forKey:#"Image"];
}
will UIImage store okay in this way?
No, UIImage doesn't conform to the NSCoding protocol.
To save an image, convert it to NSData using UIImageJPEGRepresentation(image, quality) or UIImagePNGRepresentation(image) and then you can save the NSData object in your coder because it does conform to NSCoding.
Like this:
-(id)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super init])){
title = [aDecoder decodeObjectForKey:#"Title"];
image = [UIImage imageWithData:[aDecoder decodeObjectForKey:#"ImageData"]];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:title forKey:#"Title"];
[aCoder encodeObject:UIImagePNGRepresentation(image) forKey:#"ImageData"];
}
PS, I assume you're using ARC? If you aren't you'll need to retain the values in your initWithCoder method because decodeObjectForKey: returns an autoreleased object. I've also rewritten your initWithCoder to include the normal super/nil check, which is best practice.
Note that you may wish to use self = [self init] or self = [super initWithCoder:aDecoder] instead of self = [super init], depending on what your superclass is and whether your init does any additional setup.
Coder and Decoder , which was implement in the question are OK

Resources