NSKeyedArchiver archivedDataWithRootObject: does not call encodeWithCoder - objective-c

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];

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;

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.

objective-c threading with NSobject subclass

Im using a threading class (.h/.m below) where the subclass is UIViewcontroller works without any issues.
#interface myFirstClass : UIViewController <MyOperationDelegate>{
However when I use it where the subclass is a NSobject to call a reachability class checking for internet connection, the App crashes when calling performSelectorOnMainThread? I dont understand why, there are no error when I build the App and when it crashes all i get is EXC_BAS_ACCESS. Is it not possible to do this when dealing with an NSObject? Any suggestion will be helpful for me.
#interface AppController : NSObject <MyOperationDelegate>{
myThreading.h
#protocol MyOperationDelegate
#required
-(void) updatedStatus:(NSArray*)items;
-(void) failedStatusWithError:(NSError*)error;
#end
#interface MyOperation : NSObject {
NSObject<MyOperationDelegate> * delegate;
NSOperationQueue *queue;
}
#property (retain) NSObject<MyOperationDelegate> *delegate;
-(void)load: (NSString *)stringUrlPath:(NSString *)functionAction;
#end
myThreading.m
#interface MyOperation (NSObject)
-(void)dispatchLoadingOperation:(NSDictionary *)aParameters;
#end
#implementation MyOperation
#synthesize delegate;
-(id)init
{
if ([super init]!=nil) {
queue = [NSOperationQueue new];
[queue setMaxConcurrentOperationCount:1];
}
return self;
}
-(void)load: (NSString *)stringUrlPath: (NSString *)functionAction {
[self dispatchLoadingOperation:[NSDictionary dictionaryWithObjectsAndKeys:
stringUrlPath, #"urlString", functionAction, #"action", nil]];
}
-(void)dealloc {
[queue cancelAllOperations];
self.delegate = nil;
[super dealloc];
}
-(void)dispatchLoadingOperation:(NSDictionary *)aParameters {
if([aParameters objectForKey:#"action"] == #"getStatus"){
#synchronized(self) {
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(fetchCheckStatus:)
object:aParameters];
[queue addOperation:operation];
[operation release];
}
}
}
-(void) fetchCheckStatus:(NSDictionary *)aParameters
{
NSData* data = [[NSMutableData alloc] initWithContentsOfURL:[NSURL URLWithString:[aParameters objectForKey:#"urlString"]] ];
NSError *error;
NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (responseString != nil) {
NSMutableArray *rssItems;
[self.delegate performSelectorOnMainThread:#selector(updatedStatus:) withObject:[NSArray arrayWithObjects:rssItems, nil] waitUntilDone:NO];
} else {
[queue cancelAllOperations];
[self.delegate performSelectorOnMainThread:#selector(failedStatusWithError:) withObject:error waitUntilDone:NO];
}
[responseString autorelease];
[data release];
}
#end
The problem are these lines:
NSMutableArray *rssItems;
[self.delegate performSelectorOnMainThread:#selector(updatedStatus:) withObject:[NSArray arrayWithObjects:rssItems, nil] waitUntilDone:NO];
You declare a variable rssItems but don't set it. It will contain random garbage from the stack which will then be interpreted as a pointer. Maybe sometimes you're lucky and the value is actually a pointer to a living object, but more likely dereferencing it causes your crash.
You need to actually initialize the variable, e.g.:
NSMutableArray *rssItems = nil;
but I guess you really want:
NSMutableArray *rssItems = [NSMutableArray array];

NSUserDefaults for self created classes

I'm having a problem in a usually very simple quest, I want to create a custom class containing an integer, a NSString and a NSDate. However, in my example I only use a NSString to show my problem when storing objects.
This is my class header:
#import
#interface MyClass : NSObject <NSCoding> {
NSString * myString;
}
#property (retain) NSString * myString;
#end
and my body:
#import "MyClass.h"
#implementation MyClass
#synthesize myString;
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
myString = [coder decodeObjectForKey:#"myString"];
}
return self;
}
- (id)init {
self = [super init];
if (self) {
myString = #"Hi";
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject: myString forKey:#"myString"];
}
- (void)dealloc {
myString = nil;
[super dealloc];
}
#end
My ViewController contains two buttons called Create and Read, as well as a UILabel called label.
#import <UIKit/UIKit.h>
#interface UserDefaultsTestViewController : UIViewController {
IBOutlet UILabel *label;
}
- (IBAction)pushCreate;
- (IBAction)pushLoad;
#end
I will only show the functions I've edited here:
#import "UserDefaultsTestViewController.h"
#import "MyClass.h"
- (IBAction)pushCreate {
MyClass * myClass = [[MyClass alloc] init];
NSData * data = [NSKeyedArchiver archivedDataWithRootObject:myClass];
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:data forKey:#"Test"];
[myClass release];
}
- (IBAction)pushLoad {
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
NSData * atchive = [defaults objectForKey:#"Test"];
MyClass * myClass = [NSKeyedUnarchiver unarchiveObjectWithData:atchive];
label.text = myClass.myString;
}
However, if I run my code, I get either a EXC_BAD_ACCESS error or it displays it correctly the first time and crashes when doing it a second time, again with the same error.
What am I doing wrong?
You want to do:
self.myString = #"Hi";
Or:
myString = #"Hi";
[myString retain];
Likewise, your dealloc should have:
self.myString = nil;
or
[myString release];

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.