Archiving and retrieving array from a class in Objective-c [closed] - iphone

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
Hi i am trying to save data in a file with the help of a class that is saved to disk. It is saving the data but not retrieving correct data. Plz help me
Here is my ViewController.h
#interface MIGViewController : UIViewController
- (IBAction)hideKeyboard:(id)sender;
#property (strong, nonatomic) NSMutableArray * myArray;
#property (strong, nonatomic) NSString * pathToFile;
#property (weak, nonatomic) IBOutlet UITextField *nameField;
#property (weak, nonatomic) IBOutlet UITextField *idField;
#property (weak, nonatomic) IBOutlet UITextField *addressField;
#property (weak, nonatomic) IBOutlet UITextField *phoneField;
- (IBAction)addButtonTapped:(id)sender;
- (IBAction)saveButtonTapped:(id)sender;
#end
Here is view Controller.m
#import "MIGViewController.h"
#import "MIGStudent.h"
#interface MIGViewController ()
#end
#implementation MIGViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentsPath = [paths objectAtIndex:0];
self.pathToFile = [documentsPath stringByAppendingPathComponent:#"students.sukh"];
NSFileManager * manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:self.pathToFile])
{
//File exists
self.myArray = [NSKeyedUnarchiver unarchiveObjectWithFile:self.pathToFile];
UIAlertView * alert = [[UIAlertView alloc]initWithTitle:#"Students array" message:[self.myArray description]delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil, nil];
[alert show];
}
else
{
//File doesn't exist
self.myArray = [NSMutableArray array];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)addButtonTapped:(id)sender {
NSString * name =self.nameField.text;
NSString * address = self.addressField.text;
int studentID = [self.idField.text intValue];
int phoneNumber = [self.phoneField.text intValue];
MIGStudent * student = [[MIGStudent alloc] initWithName:name address:address studentID:studentID phoneNumber:phoneNumber];
[self.myArray addObject:student];
self.nameField.text = #"";
self.addressField.text = #"";
self.idField.text = #"";
self.phoneField.text = #"";
}
- (IBAction)saveButtonTapped:(id)sender {
[NSKeyedArchiver archiveRootObject:self.myArray toFile:self.pathToFile];
}
- (IBAction)hideKeyboard:(id)sender {
[self resignFirstResponder];
}
#end
Here is my class Student.h
#interface MIGStudent : NSObject <NSCoding>
#property (strong, nonatomic) NSString * name;
#property (strong, nonatomic) NSString * address;
#property (nonatomic) int studentID;
#property (nonatomic) int phoneNumber;
-(id)initWithName:(NSString *) name address:(NSString *)address studentID: (int)studentID phoneNumber:(int) phoneNumber;
#end
And here is Student.m
#import "MIGStudent.h"
#implementation MIGStudent
-(id) initWithName:(NSString *)name address:(NSString *)address studentID:(int)studentID phoneNumber:(int)phoneNumber
{
if (self=[super init])
{
self.name = name;
self.address = address;
self.phoneNumber = phoneNumber;
self.studentID = studentID;
}
return self;
}
-(void) encodeWithCoder:(NSCoder *)aCoder
{
//Used when saving to disk
[aCoder encodeObject:self.name forKey:#"name"];
[aCoder encodeObject:self.address forKey:#"address"];
[aCoder encodeInt:self.phoneNumber forKey:#"phoneNumber"];
[aCoder encodeInt:self.studentID forKey:#"studentID"];
}
-(id) initWithCoder:(NSCoder *)aDecoder
{
//Used when reading from disk
self.name = [aDecoder decodeObjectForKey:#"name"];
self.address = [aDecoder decodeObjectForKey:#"address"];
self.phoneNumber = [aDecoder decodeIntForKey:#"phoneNumber"];
self.studentID = [aDecoder decodeIntForKey:#"studentID"];
return self;
}
#end
And here is what i get output every time
I think everythings is working properly but there is some problem while retrieving the data
Thanks in advance

That's not a problem with your implementation of NSCoding, that's actually not even a problem at all! That's the way an NSArray prints itself literally (the result of the -description method). The fact that it does this indicates that the archiving/de-archiving process went smoothly. The array is the set of parenthesis (), and the objects within are in the format <Class : memory address>. If you had an array of invalid objects, then your array would most likely refuse to print, or crash when the students objects were added to it.
You can take advantage of this in your MIGStudent class and override -description to print a friendlier format. For example:
-(NSString*)description {
return [NSString stringWithFormat:#"<%# : %p Student named: %# - who lives at: %# With the ID number: %d and the phone number: %d>", NSStringFromClass(self.class), self, self.name, self.address, self.studentID, self.phoneNumber];
}
As a sidenote, initializers always call through to super. Your initWithCoder: method will always return nil.

Your initWithCoder: method is incorrect it will fail as currently it will return nil or some garbage value, it should be:
-(id) initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self)
{
//Used when reading from disk
self.name = [aDecoder decodeObjectForKey:#"name"];
self.address = [aDecoder decodeObjectForKey:#"address"];
self.phoneNumber = [aDecoder decodeIntForKey:#"phoneNumber"];
self.studentID = [aDecoder decodeIntForKey:#"studentID"];
}
return self;
}
Secondly you can either override description or debugDescription method of your class to return custom information about that class.

Related

writing data using NSKeyedArchiver

I trying to learn how to save array of objects using NSKeyedArchiver and I coded a small application to do that and I logged to see if the array was saved but everytime I get 0 for array count and here is the code.
ViewController.h
#interface ViewController : UIViewController
{
IBOutlet UITextField *text;
IBOutlet UITextField *textName;
IBOutlet UITextField *textAge;
IBOutlet UILabel *name;
IBOutlet UILabel *age;
BOOL flag;
BOOL choice;
NSString *documentDirectory;
NSMutableArray *anArray;
Person *p;
NSData *data;
}
-(BOOL) dataFilePath;
-(IBAction)readPlist;
-(IBAction) writePlist;
#property (strong,nonatomic)IBOutlet UITextField *text;
#property (strong,nonatomic)IBOutlet UITextField *textName;
#property (strong,nonatomic)IBOutlet UITextField *textAge;
#property (strong,nonatomic)IBOutlet UILabel *name;
#property (strong,nonatomic)IBOutlet UILabel *age;
#property (strong,nonatomic)NSString *documentDirectory;
#property (strong,nonatomic)NSMutableArray *anArray;
#end
ViewController.m
#interface ViewController ()
#end
#implementation ViewController
#synthesize text,documentDirectory,textAge,textName,name,age,anArray;
- (void)viewDidLoad
{
[super viewDidLoad];
// checking if the file was created and show a message if its created or not.
if ([self dataFilePath]) {
NSLog(#"File Created !");
} else {
NSLog(#"File Not Created !");
}
NSLog(#"File location : %#",documentDirectory);
choice = YES;
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(BOOL) dataFilePath
{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
documentDirectory = [path objectAtIndex:0];
documentDirectory = [documentDirectory stringByAppendingPathComponent:#"MilmersĀ­Data.dat"];
return TRUE;
}
- (IBAction)writePlist
{
p.name = textName.text;
p.age = [textAge.text intValue];
[anArray addObject:p];
for (int i=0; i<[anArray count]+1; i++) {
Person *pp = [[Person alloc]init];
pp=[anArray objectAtIndex:i];
NSLog(#"Name: %#",pp.name); // checking the names in pp object but getting null
}
data = [NSKeyedArchiver archivedDataWithRootObject:anArray];
[data writeToFile:documentDirectory options:NSDataWritingAtomic error:nil];
NSLog(#"Array length: %d",[anArray count]); //Always got array count zero.
}
-(IBAction)readPlist
{
NSString *filePath = documentDirectory;
NSMutableArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(#"The array is: %#",array); // it shows that there is nothing in the array
}
#end
I wrote the class for writing .plist files originally but I knew later that I cant store objects in .plist file so I tried so that with archive, thats why the method name have plist in it.
Thank you in advance
Looks like you aren't ever creating an instance of p to add to the array. Try:
Person *p = [[Person alloc] init];
p.name = textName.text;
p.age = [textAge.text intValue];
[anArray addObject:p];
your index limit was also wrong in this loop
for (int i=0; i<[anArray count]; i++) {
NSLog(#"Name: %#", [[anArray objectAtIndex:i] name]);
}
you should really have been seeing a couple of different crashes...
Try adding this in viewDidLoad
[[NSFileManager defaultManager] createFileAtPath:documentDirectory contents:nil error:nil];
It looks like you never do this, and using archives to write to files only works if the file already exists (make sure you only do this once, otherwise every time that view is loaded the file will be emptied of all the data in it). And when you do this
if ([self dataFilePath])
It's pointless, because no matter what it always returns yes, whether the file exists or not.
Does your Person class implement NSCoding?
Specifically you need to implement something like the following in Person.m:
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (!self) {
return nil;
}
self.name = [decoder decodeObjectForKey:#"name"];
self.age = [decoder decodeObjectForKey:#"age"];
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.name forKey:#"name"];
[encoder encodeObject:self.age forKey:#"age"];
}

Accessing changeable values in a singleton?

First off, I come from Lua, don't blame me for being global variable minded lol. So, I've been reading up on how to use this whole "Singleton system" and I'm not sure if I'm completely missing the point or if I'm just implementing it incorrectly?
The goal of my code is to create a way for multiple files to access a variable that holds the size of an array in a specific file. Here is my singleton:
.h
#import <Foundation/Foundation.h>
#interface GlobalVariables : NSObject
{
NSNumber *currentGameArrayCount;
BOOL *isGamePaused;
}
#property (nonatomic, readwrite) NSNumber *currentGameArrayCount;
#property (nonatomic, readwrite) BOOL *isGamePaused;
+ (GlobalVariables *)sharedInstance;
#end
.m
#import "GlobalVariables.h"
#implementation GlobalVariables
#synthesize currentGameArrayCount, isGamePaused;
static GlobalVariables *gVariable;
+ (GlobalVariables *)sharedInstance
{
if (gVariable == nil) {
gVariable = [[super allocWithZone:NULL] init];
}
return gVariable;
}
- (id)init
{
self = [super init];
if (self)
{
currentGameArrayCount = [[NSNumber alloc] initWithInt:0];
isGamePaused = NO;
}
return self;
}
#end
and in another file with the array I use:
GlobalVariables *sharedData = [GlobalVariables sharedInstance];
NSNumber *tmpArrayCount = [sharedData currentGameArrayCount];
NSInteger tmpCount = [whereStuffActuallyHappens.subviews count]; // Subviews is the array
NSNumber *currentCount = [NSNumber numberWithInteger:tmpCount];
tmpArrayCount = currentCount;
the hope of this code was to get the variable in the singeton (currentGameArrayCount) and set it too what the current array count was (currentCount). Am I incorrectly interpreting the purpose of a singleton? Am I just bad at singletons and didn't set it up correctly? Does anyone know how I could achieve the result of getting my array count to be accesible to all my files?
You have a few issues. Try these changes:
GlobalVariables.h:
#import <Foundation/Foundation.h>
#interface GlobalVariables : NSObject
#property (nonatomic, assign) int currentGameArrayCount;
#property (nonatomic, assign) BOOL gamePaused;
+ (GlobalVariables *)sharedInstance;
#end
GlobalVariables.m:
#import "GlobalVariables.h"
static GlobalVariables *gVariable = nil;
#implementation GlobalVariables
+ (GlobalVariables *)sharedInstance {
if (gVariable == nil) {
gVariable = [[self alloc] init];
}
return gVariable;
}
- (id)init {
self = [super init];
if (self) {
self.currentGameArrayCount = 0;
self.gamePaused = NO;
}
return self;
}
#end
Now in your other code you can do:
GlobalVariables *sharedData = [GlobalVariables sharedInstance];
int tmpArrayCount = sharedData.currentGameArrayCount;
NSInteger tmpCount = [whereStuffActuallyHappens.subviews count]; // Subviews is the array
sharedData.currentGameArrayCount = tmpCount;

Variables emptying or erasing on didSelectRowAtIndexPath

For some reason, I can't access any of my variables after the first IF Statement in the following code. For instance, if index path is [0,0], then the variable phoneText spits out a phone number. But if its [1,0] or [2,0], I get a "null" return. Why is my variable being erased?
The following function in mapviewcontroller.m sets the values. I do actually have an error here that says "instance method setDetails not found".
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
//this determines what kind of item was selected
if ([control isKindOfClass:[UIButton class]]) {
NSLog(#"Trying to load VenueIdentifier...");
FinderAnnotation *clicked = view.annotation;
FinderViewController *fvi = [self.storyboard instantiateViewControllerWithIdentifier:#"FinderDetail"];
NSString* latitude = [NSString stringWithFormat:#"%f",clicked.coordinate.latitude];
NSString* longitude = [NSString stringWithFormat:#"%f",clicked.coordinate.longitude];
NSLog(#"lat: %#",latitude);
NSLog(#"lon: %#",longitude);
[fvi setDetails:clicked.title phone:clicked.phone address:clicked.address beersavailable:clicked.beersavailable latitude:latitude longitude:longitude];
[self.navigationController pushViewController:fvi animated:YES];
}
}
Then my finderdetail.h creates these variables:
#interface FinderDetail : UITableViewController{
UITableViewCell *phone;
UITableViewCell *address;
UITableViewCell *directions;
UILabel *venueLabel;
NSString *phoneText;
NSString *addressText;
NSString *venueText;
NSString *beersavailable;
NSString *latitudeText;
NSString *longitudeText;
}
#property (nonatomic, retain) IBOutlet UITableViewCell *phone;
#property (nonatomic, retain) IBOutlet UITableViewCell *address;
#property (nonatomic, retain) IBOutlet UITableViewCell *directions;
#property (nonatomic, retain) IBOutlet UILabel *venueLabel;
#property (nonatomic, retain) NSString *phoneText;
#property (nonatomic, retain) NSString *addressText;
#property (nonatomic, retain) NSString *venueText;
#property (nonatomic, retain) NSString *beersavailble;
#property (nonatomic, retain) NSString *latitudeText;
#property (nonatomic, retain) NSString *longitudeText;
#end
Lastly, finderdetail.m grabs these values, assigns them to the variables, and spits them into the table:
#implementation FinderDetail
#synthesize venueLabel, phone, address, directions;
#synthesize phoneText, addressText, venueText, beersavailble, latitudeText, longitudeText;
NSString *notlisted;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
-(void)setDetails:(NSString *)v phone:(NSString *)p address:(NSString *)a beersavailable:(NSString *)ba latitude:(NSString *)lat longitude:(NSString *)lon
{
NSLog(#"venue: %#",v);
NSLog(#"phone: %#",p);
NSLog(#"address: %#",a);
NSLog(#"beersavailable: %#",ba);
NSLog(#"%#",lat);
NSLog(#"%#",lon);
latitudeText = lat;
longitudeText = lon;
phoneText = p;
addressText = a;
venueText = v;
beersavailble = ba;
NSLog(#"%#", latitudeText);
NSLog(#"%#", longitudeText);
notlisted = #"Not Listed";
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Latitude: %#", latitudeText);
NSLog(#"Longitude: %#", longitudeText);
phone.detailTextLabel.text = phoneText;
address.detailTextLabel.text = addressText;
self.venueLabel.text = venueText;
if(phoneText == nil){
phone.detailTextLabel.text = notlisted;
}
if(addressText == nil){
address.detailTextLabel.text = notlisted;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//#warning Incomplete method implementation.
// Return the number of rows in the section.
if(section ==0)
return 1;
else
if(section ==1)
return 1;
else
if(section ==2)
return 1;
else
return 0;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"%#",indexPath);
if((indexPath.section==0) && (indexPath.row ==0))
{
NSLog(#"%#",phoneText);
}
if((indexPath.section==1) && (indexPath.row ==0))
{
NSLog(#"%#",addressText);
}
if((indexPath.section==2) && (indexPath.row ==0))
{
NSLog(#"%#",latitudeText);
NSLog(#"%#",longitudeText);
}
}
The initial phoneText will display in an NSLog, but the addressText and latitudeText and longitudeText return null. I can put phoneText in one of those lower if statements and it too returns null. Thanks!!!
You aren't actually using your #property when you are doing the following:
latitudeText = lat;
longitudeText = lon;
phoneText = p;
addressText = a;
venueText = v;
beersavailble = ba;
Also, you are leaking memory every time those assignments are performed after the initial time (when they were still nil).
What you really want is:
self.latitudeText = lat;
self.longitudeText = lon;
self.phoneText = p;
self.addressText = a;
self.venueText = v;
self.beersavailble = ba;
Also, with a NSString (also NSData, NSSet, etc.) #property, it is better to define them as a copy, since it would be perfectly valid to pass in a NSMutableString instead (since it is a subclass of NSString), which then the contents could be altered externally of this object:
#property (nonatomic, copy) NSString *phoneText;
#property (nonatomic, copy) NSString *addressText;
#property (nonatomic, copy) NSString *venueText;
#property (nonatomic, copy) NSString *beersavailble;
#property (nonatomic, copy) NSString *latitudeText;
#property (nonatomic, copy) NSString *longitudeText;
Finally, the fact that you get (NULL) outputted by NSLog suggests the ivars are getting set to nil (and most likely released), and you are using ARC (Automatic Reference Counting), instead of manual retain/release/autorelease.
In setDetails you need to use the properties in order to retain the objects and release previous objects. Assigning directly to the ivars subverts the properties setters/getters and the memory management they provide is lost. Basically if properties are defined use them every time.
Since the objects are not being retained their memory can be reused and unpredictable results can occur such as the values becoming nil.
One way to find such problems is to turn on NSZombies in the simulator runs. I do this occasionally even when I am not having problems just as a check.
To fix the problem rewrite setDetails as:
-(void)setDetails:(NSString *)v phone:(NSString *)p address:(NSString *)a beersavailable:(NSString *)ba latitude:(NSString *)lat longitude:(NSString *)lon
{
self.latitudeText = lat;
self.longitudeText = lon;
self.phoneText = p;
self.addressText = a;
self.venueText = v;
self.beersavailble = ba;
self.notlisted = #"Not Listed";
}
One way to insure that properties are not inadvertently not used is to define the ivars with a slightly different name than the properties. The synthesize statement supports this. Here is how:
in the #interface:
NSString *_latitudeText;
...
#property (nonatomic, retain) NSString *latitudeText;
in the #implementation
#synthesize latitudeText = _latitudeText;

Memory management of container classes

I've made a container class to store a single tweet. Its initialized by passing in a dictionary object which is a single tweet.
I then store an array of these 'tweets' which I process through to display in a table.
The project is now finished and I am reviewing everything at the moment and I was wondering is there a better way to do this in the future. Is the memory handled correctly. I declare the string member vars with 'copy' and later in the dealloc I use a 'release' rather than just setting them to 'nil'.
Is my init ok or could that be improved?
Tweet.h
#import
#interface Tweet : NSObject
{
NSString * _userName;
NSString * _tweetText;
NSString * _tweetURL;
}
#property (nonatomic, copy) NSString * userName;
#property (nonatomic, copy) NSString * tweetText;
#property (nonatomic, copy) NSString * tweetURL;
- (id) initWithDict:(NSDictionary *)productsDictionary;
#end
Tweet.m
#implementation Tweet
#synthesize userName = _userName;
#synthesize tweetText = _tweetText;
#synthesize tweetURL = _tweetURL;
- (id) initWithDict:(NSDictionary *)productsDictionary
{
NSDictionary *aDict = [productsDictionary objectForKey:#"user"];
self.userName = [aDict objectForKey:#"screen_name"];
self.tweetText = [productsDictionary objectForKey:#"text"];
NSRange match;
match = [self.tweetText rangeOfString: #"http://"];
if (match.location != NSNotFound)
{
NSString *substring = [self.tweetText substringFromIndex:match.location];
NSRange match2 = [substring rangeOfString: #" "];
if (match2.location == NSNotFound)
{
self.tweetURL = substring;
}
else
{
self.tweetURL = [substring substringToIndex:match2.location];
}
}
else
{
self.tweetURL = nil;
}
return self;
}
-(void) dealloc
{
[self.tweetText release];
[self.tweetURL release];
[self.userName release];
[super dealloc];
}
#end
Many Thanks,
Code
At first sight, I see no inherent flaws here. That looks fine. I would prefer to do:
-(void) dealloc
{
[_tweetText release];
[_tweetURL release];
[_userName release];
[super dealloc];
}
But what you do is good as well.

Not Able to Add Object to NSMutableArray

Hey guys, I have this code within a function inside a class that is a subclass of NSOperation:
//...
#implementation DataLoader
#synthesize addedAnnotations;
#synthesize addedOverlays;
#synthesize loaderFunc;
#synthesize DLDelegate;
//...
-(id)initWithFunction:(LoaderFunc)func withDelegate:(id)delegate {
if (self = [super init]) {
self.addedOverlays = nil;
self.addedAnnotations = nil;
self.loaderFunc = func;
self.DLDelegate = delegate;
return self;
}
return nil;
}
//...
//inside a function
for (ParkingAnnotations *annotation in fetchedObjects) {
ParkingAnnotation *parkingAnnot = [[ParkingAnnotation alloc] init];
workingCoordinate.latitude = [[annotation latitude] doubleValue];
workingCoordinate.longitude = [[annotation longitude] doubleValue];
[parkingAnnot setCoordinate:workingCoordinate];
[parkingAnnot setTitle:[annotation valueForKey:#"lotName"]];
[parkingAnnot setAnnotationType:[annotation iconTypeRaw]];
[self.addedAnnotations addObject:parkingAnnot];//parkingAnnot not added to array here
[parkingAnnot release];
}
//...
Added annotations is an NSMutable array, I have been walking through this code with the debugger and for some reason the parkingAnnot object is not getting added to the array. Here is the relevant header code for the class:
//...
#interface DataLoader : NSOperation {
NSMutableArray *addedAnnotations;
NSMutableArray *addedOverlays;
LoaderFunc loaderfunc;
id <DataLoaderProtocol> DLDelegate;
}
#property (nonatomic, retain) NSMutableArray* addedAnnotations;
#property (nonatomic, retain) NSMutableArray* addedOverlays;
#property (nonatomic) LoaderFunc loaderFunc;
#property (assign) id DLDelegate;
//...
It is an astonishing problem because the function in which I am experiencing the problem was copied from my MapViewController and is essentially the same, but instead of mapView addAnnotation: I am adding to an NSMutable array instead. Any idea of what's up? Thanks in advance!
Where are you actually instantiating the addedAnnotations array? I only see it being assigned nil in your initialize function, maybe it should change to something like:
self.addedAnnotations = [[[NSMutableArray alloc] init] autorelease];