Debugging Archiver - ios

I cannot figure out what is wrong with my archiving and unarchiving. I am trying to save data from a class. The encoder and decoder are:
//archiving
- (void)encodeWithCoder:(NSCoder *)aCoder
{
//encode only certain instance variables
[aCoder encodeObject:self.name forKey:#"name"];
[aCoder encodeObject:self.location forKey:#"location"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [self init];
if (self) {
//decode data
[self setName:[aDecoder decodeObjectForKey:#"name"]];
[self setLocation:[aDecoder decodeObjectForKey:#"location"]];
}
return self;
}
The instance variables are properties, and they are values of a custom class. Multiple instances of this class populate an NSMutableArray stored in my main view controller.
My view controller contains the methods:
- (NSString *)itemArchivePath
{
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//only one document in list - get path t o it
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
return [documentDirectory stringByAppendingString:#"file.archive"];
}
- (BOOL)saveChanges
{
//returns success or failure
NSString *path = [self itemArchivePath];
return [NSKeyedArchiver archiveRootObject:customClassArray //This is the array storing multiple instances of the custom class
toFile:path];
}
The comment about the array is not in the actual code. And finally, the app delegate has the following code:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
BOOL success = [mainViewController saveChanges];
if (success) {
NSLog(#"Saved Successfully!");
}
else {
NSLog(#"Unsuccessful");
}
}
Unfortunately, whenever running the code, "unsuccessful" is always logged, and I am not sure why. The mainViewController is an instance variable of the app delegate. I have tried debugging for a very long time, and I cannot find the problem. Any help is appreciated. Thanks!

There's a good chance that the path to which you're trying to save isn't valid. You should try logging it and see whether the value is sane when the save fails. You might also want to look at using stringByAppendingPathComponent: instead of stringByAppendingString:.

Related

NSString value nil inside onEnterTransitionDidFinish method

I guess its a very basic memory concept. But couldn't figure it out what happens with below case. Any insight would be helpful.
This could be similar to Problems with NSString inside viewWillDisappear
But I wanted to know why there requires a #property. How can we do it without taking #property. Please provide some inside view.
in .h I have NSString *someString
in .mm (this is my non-ARC cocos2d+box2d game scene)
-(id)initWithString:(NSString *)tempString
{
if(self = [super init])
{
someString = [[NSString allo]init];
someString = tempString;
}
return self;
}
-(void)onEnterTransitionDidfinish
{
[super onEnterTransitionDidfinish];
NSLog("The String is %#",someString);//Becomes nil here
}
-(void)printString
{
NSLog(#"The String is %#",someString);//This works fine
}
If you are not using ARC then you need to learn a lot more about memory management.
The following two lines:
someString = [[NSString allo]init];
someString = tempString;
should be:
someString = [tempString copy]; // or [tempString retain];
And be sure you call [someString release] in your dealloc method.
BTW - you are not using a property. someString is declared as an instance variable, not a property.

iOS - custom initialization method for view controllers when using storyboard

I've read several posts similar to this one but I found them too specific. What I really want is a more general answer. According to Apple's view controller programming guide, viewDidLoad: should be used to "Allocating or loading data to be displayed in your view". If I have some data that has nothing to do with display, where should I initialize them?
Some posts suggests that initialization could be done in initWithCoder: when the view controller is initialized via a storyboard. I've tried to initialize an array in initWithCoder:, but after that it turned out that the array is still empty. So can we write a designated initializer to initialize this kind of data?
Here is the code:
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
// load notes
NSString * path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
_notes = [[PWCNotes alloc] initNotesWithFilename:self.docTitle path:path numberOfPages:self.numberOfPages];
_index = 0;
}
return self;
}
Here is the designated initializer method for PWCNotes
- (id)initNotesWithFilename:(NSString *)fileName path:(NSString *)path numberOfPages:(int)numberOfPages
{
if (!(self = [super init])) {
return nil;
}
_filePath = [path stringByAppendingString:[NSString stringWithFormat:#"/%#/notes.txt", fileName]];
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:self.filePath];
if (!exists)
{
// if file does not exist, create one and initialize the content
_notes = [[NSMutableArray alloc] init];
[[NSFileManager defaultManager] createFileAtPath:self.filePath contents:nil attributes:nil];
NSString * emptyString = #"Add Notes Here!";
for (int i = 0; i < numberOfPages; ++i)
{
[self.notes addObject:emptyString];
}
// write content of the array to the file
[self.notes writeToFile:self.filePath atomically:YES];
}
else
{
// otherwise, load it from the text file
_notes = [[NSMutableArray alloc] initWithContentsOfFile:self.filePath];
}
return self;
}
PWCNotes class has as a property a mutable array of NSString *s. When I call [self.notes.notes getObjectAtIndex:self.index], an NSRangeException is thrown, saying that I'm trying to access the object at index 0 in an empty array. Am I missing something?
viewDidLoad is the best place if you need to use the data only once the view appears, which is usually the case for View Controllers, as they only exists for managing views.
It's the best place because it is the one that's called in any case, no matter how the class got initialized.
If you really wanted to have the data ready earlier, you could implement an +initialize or +load method (NSObject +load and +initialize - What do they do?) instead, but that's not really what you should do in a View Controller.

How to store a CLLocation object using NSKeyedArchiver?

I am slightly confused why the following doesn't work. Can someone enlighten me?
I have one function that returns a filePath:
- (NSString *) lastLocationPersistenceFilePath {
NSString *filePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:#"last_location"];
return filePath;
}
Then, in a method triggered by a button, I attempt to save a CLLocation object like so:
// Store this location so it can persist:
BOOL success = [NSKeyedArchiver archiveRootObject:_startLocation toFile:[self lastLocationPersistenceFilePath]];
if (!success) {
NSLog(#"Could not persist location for some reason!");
} else {
NSLog(#"New location has been stored.");
}
In my viewDidLoad:, I attempt to load it:
- (void)viewDidLoad
{
[super viewDidLoad];
// Retrieve last location:
CLLocation *decodedLocation = [NSKeyedUnarchiver unarchiveObjectWithFile:[self lastLocationPersistenceFilePath]];
if (decodedLocation) {
_startLocation = decodedLocation;
} else {
NSLog(#"Decoding failed.");
}
}
The archiving fails which, of course, means I can't decode anything either. I feel like I am missing something obvious/trivial... any pointers?
Simply, it is not possible to write to the the app's resource directory. Instead write to the app's document directory.
Code example"
- (NSString *) lastLocationPersistenceFilePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths firstObject];
NSString *filePath = [documentDirectory stringByAppendingPathComponent:#"last_location"];
return filePath;
}

Objective-C read/write to plist file //after trying 9999 different solutions

Like I said in the subject, I'm facing some problems with both read and write to a .plist file. I already read a lot of possible implementations from other threads here and from a book, but still I can't make this to work, so I'd like to ask for your opinion. Right now this goes like this:
I have an array of movies arrayMovies in my shared instance data manager. I work my whole app based on this array. It's always "reachable" from every part of the app with just this:
[[iCultureDataManager sharedInstance]arrayMovies];
So when the app finishes loading, I initialize the arrayMovies from an array that is supposed to be in the .plist file, like this:
-(NSString* ) getFilePathForMovies{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentsDirectory = [path objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingString:#"movies.plist"];
return filePath;
}
- (NSArray *) getMovies{
NSString *myPath = [self getFilePathForMovies];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:myPath];
NSArray* movies;
if(fileExists){
movies = [[NSArray alloc ]initWithContentsOfFile:myPath];
}
return movies;
}
and in the init I just
_arrayMovies = [NSMutableArray arrayWithArray:[self getMovies]];
One thing I don't understand is why does the BOOL fileExists always returns false, like it's not finding the .plist file.
To save the array for the .plist file I do:
- (void)applicationDidEnterBackground:(UIApplication *)application {
[self addToMovies];
NSLog(#"DidEnterBackground: Adding to Movies...");
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
[self addToMovies];
NSLog(#"applicationWillEnterForeground: Adding to Movies...");
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
/*
Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
*/
}
- (void)applicationWillTerminate:(UIApplication *)application {
NSLog(#"applicationWillTerminate: Adding to Movies...");
[self addToMovies];
}
- (void) addToMovies{
NSString *myPath = [self getFilePathForMovies];
[[[iCultureDataManager sharedInstance]arrayMovies] writeToFile:myPath atomically:YES];
}
-(NSString* ) getFilePathForMovies{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentsDirectory = [path objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingString:#"movies.plist"];
return filePath;
}
I think it's pretty much this for the matters but if I can help you with some more information feel free to say, cause I really don't understand what is going on. I don't know if I'm writing to file wrong or reading it wrong or whatever else I'm doing wrong.
Thank you very much for your time and help,
PS: now I'm at the point where the fileExists always returns -1... :S
You imply that you are initializing your array with a list of movies that are in the plist file. That would imply that you are starting with a plist file. To access the read-only plist file that exists in your bundle, you use a different path like this:
-(NSString *)getOriginalFilePathForMovies {
NSString* plistPath = [[NSBundle mainBundle] pathForResource:#"originalMovies" ofType:#"plist"];
contentArray = [NSArray arrayWithContentsOfFile:plistPath];
}
Then to access the writable plist you need a path like you are using:
-(NSString* ) getFilePathForMovies{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *documentsDirectory = [path objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingString:#"movies.plist"];
return filePath;
}
So I would recommend doing this to initialize your array:
- (NSArray *)getMovies {
NSString *myPath = [self getFilePathForMovies];
BOOL fileExists = [[NSFileManager defaultManager]fileExistsAtPath:myPath];
NSArray* movies;
if(fileExists){
movies = [[NSArray alloc]initWithContentsOfFile:myPath];
} else {
movies = [[NSArray alloc]initiWithContentsOfFile:[self getOriginalFilePathForMovies]];
return movies;
}
Instead of stringByAppendingString:, you should be using stringByAppendingPathComponent:. Otherwise, your path will look like .../Documentsmovies.plist (I think).
EDIT: Oh, someone already said in the comments!

Saving Array easy way

I have used NSuserDefaults and NSkeyedArchive before but i dont think it will work for my new project..
I get data back from JSON and store it in an array (name,age,country) (all NSString)
i want to make a save button in the detail view so that it saves that person's data.
And show the saved data in another tableview. (for loop on the array and get all objects back)
How should i handle this in a easy way.. i except max 40 stored names so its not so heavy..
So in short i want a function like you see in "home app's" where you can "favorite/store a house"
-- Update
viewDidLoad
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [docDir stringByAppendingPathComponent:#"Names.plist"];
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
arrayWithNames = [[NSMutableArray alloc]init];
[arrayWithNames addObjectsFromArray:array];
Savebutton
NSMutableArray *nameInfo = [[NSMutableArray alloc]initWithObjects:self.name,self.age,self.country, nil];
[arrayWithNames addObjectsFromArray:nameInfo];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"Names.plist"];
[arrayWithNames writeToFile:path atomically:YES];
This works but i get all data together instead of every array as an independent object
btw i made sure there cant be a NULL :)
As long as all of the data is NSString values, as you say, you can just use writeToFile:atomically: to save an array to a file. However, JSON sometimes contains nulls, which aren't compatible with that method. If you try to use that method when nulls are present, it will throw an exception. If there's any chance of nulls (and there almost always is a chance), you'll need to take some precautions. A couple of possibilities:
Make mutable copies of your data, run through it, and remove nulls or replace them with something else (like an empty string).
Convert the data back to JSON via [NSJSONSerialization dataWithJSONObject:options:error:] and then write the resulting NSData to a file.
I not quite understand your question.
But in your case what I did was create a Model with the structure of information I intended to store (in your case looked Person) and created an array in which i will add the objects Person
Could use several cases to save, but in my opinion, the simplest would be through the NSUserDefaults (the solution depends heavily on your database).
Soo, you will have the model Person
import <Foundation/Foundation.h>
#interface Person : NSObject
#property(nonatomic,strong) NSString *name;
#property(nonatomic,strong) NSString *country;
#property(nonatomic,strong) NSString *age;
...
With the methods for the encryption:
- (void)encodeWithCoder:(NSCoder *)encoder {
//Encode properties, other class variables, etc
[encoder encodeObject:self.name forKey:#"name"];
[encoder encodeObject:self.age forKey:#"age"];
[encoder encodeObject:self.country forKey:#"country"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
//decode properties, other class vars
self.name = [decoder decodeObjectForKey:#"name "];
self.age = [decoder decodeObjectForKey:#"age"];
self.country = [decoder decodeObjectForKey:#"country"];
}
return self;
}
Then you create a NSMutableArray where you add your objects.
[arrayPeople addObject:person];
When you decide to store in your application data you can do this:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *myEncodedObjectShopping = [NSKeyedArchiver archivedDataWithRootObject:arrayPeople];
[defaults setObject:myEncodedObjectShopping forKey:#"people"];
To retrive the data:
NSData *myDecodedObject = [defaults objectForKey:#"people"];
NSMutableArray *decodedArray =[NSKeyedUnarchiver unarchiveObjectWithData: myDecodedObject];

Resources