writing data using NSKeyedArchiver - iphone

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

Related

Getting error class is not key value coding-compliant for the key name

I am implementing SearchBar in my TableView. But it will display error when i try to enter text in SearchBar.
This is my NSObject class code .h file :
#import <Foundation/Foundation.h>
#interface ChannelList : NSObject
{
NSString *channelLink;
NSString *channelName;
NSString *channelType;
NSString *channelLogo;
}
#property (nonatomic, copy) NSString *channelName;
#property (nonatomic, copy) NSString *channelType;
#property (nonatomic, copy) NSString *channelLogo;
#property (nonatomic, copy) NSString *channelLink;
+ (id)channelLink:(NSString*)channelLink channelName:(NSString*)achannelName channelType:(NSString*)achannelType channelLogo:(NSString*)achannelLogo;
#end
And .m file :
#import "ChannelList.h"
#implementation ChannelList
#synthesize channelLogo,channelName,channelType,channelLink;
+ (id)channelLink:(NSString*)channelLink channelName:(NSString*)achannelName channelType:(NSString*)achannelType channelLogo:(NSString*)achannelLogo{
ChannelList *ChannelList = [[self alloc] init];
[ChannelList setChannelLink:channelLink];
[ChannelList setChannelName:achannelName];
[ChannelList setChannelType:achannelType];
[ChannelList setChannelLogo:achannelLogo];
return ChannelList;
}
#end
Using this array to populate my table view
channelAllData = [NSArray arrayWithObjects:
[ChannelList channelLink:#"http://cdn.m.yuppcdn.net/liveorigin/smil:ndtvhindi_iphone.smil/playlist.m3u8" channelName:#"NDTV" channelType:#"NEWS" channelLogo:#"ndtv.png"],[ChannelList channelLink:#"http://cdn.m.yuppcdn.net/liveorigin/smil:aajtak_iphone.smil/playlist.m3u8" channelName:#"Aaj_Tak" channelType:#"NEWS" channelLogo:#"Aaj_Tak"],[ChannelList channelLink:#"http://cdn.m.yupptv.tv/liveorigin/smil:indiatv.smil/playlist.m3u8" channelName:#"India_tv" channelType:#"NEWS" channelLogo:#"India_tv_logo.gif"],[ChannelList channelLink:#"http://cdn.m.yuppcdn.net/liveorigin/smil:headlinetoday_iphone.smil/playlist.m3u8" channelName:#"HeadlinesToday" channelType:#"NEWS" channelLogo:#"HeadlinesToday.png"],nil];
searchedData = [NSMutableArray arrayWithCapacity:[channelAllData count]];
Here searchedData is the search result of my SearchBar.
And here I'm populating my searchedData, and getting error as I mentioned in title.
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
// remove all data that belongs to previous search
[searchedData removeAllObjects];
if([searchText isEqualToString:#""]||searchText==nil){
[self.tableView reloadData];
return;
}
[self filterContentForSearchText:searchText];
// NSLog(#"%i",searchedData.count);
[self.tableView reloadData];
}
- (void)filterContentForSearchText:(NSString*)asearchText
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name contains[c] %#",asearchText];
NSLog(#"predicate %#",predicate);
NSArray *tempArray = [channelAllData filteredArrayUsingPredicate:predicate];
searchedData = [NSMutableArray arrayWithArray:tempArray];
}
In the predicate, you're using self.name whereas there's no name property declared on your class. Did you mean self.channelName instead?

how to decode multiple objects from data stored in file in iOS

I have a form with information first name and last name and some other information. I use a person class to store this information. on submit click I archiving it in a file person.txt using NSCoding implemented in person class. if I add multiple persons in the file person.txt, how can I get all the person objects stored in the file. decoding the person class just gives me the last added person.
If you want all of the person objects serialized, then you need the NSArray or whatever other collection class in which they are stored to be the root object for the NSKeyedArchiver. e.g.: (assumes ARC)
#import <Foundation/Foundation.h>
#interface Person:NSObject <NSCoding>
#property (nonatomic, copy) NSString *lastName;
#property (nonatomic, copy) NSString *firstName;
// etc.
#end
#implementation Person
#synthesize lastName = _lastName;
#synthesize firstName = _firstName;
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.lastName forKey:#"ln"];
[aCoder encodeObject:self.firstName forKey:#"fn"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if( !self ) { return nil; }
_lastName = [aDecoder decodeObjectForKey:#"ln"];
_firstName = [aDecoder decodeObjectForKey:#"fn"];
return self;
}
#end
int main(int argc, char *argv[]) {
NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
Person *me = [Person new];
me.lastName = #"Kitten";
me.firstName = #"Mittens";
Person *you = [Person new];
you.lastName = #"Youe";
you.firstName = #"JoJo";
NSArray *people = [NSArray arrayWithObjects:me,you,nil];
NSData *serializedData = [NSKeyedArchiver archivedDataWithRootObject:people];
// write your serializedData to file, etc.
[p release];
}
Why the .txt extension on your archive, though? It just binary data, right?

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.

return a static const []

So in my model I have the following code... I am successfully able to return each individual value. I want to know how am I able to return the entire speakerTable []... Maybe some advice. Thanks!
typedef struct {
NSUInteger speakerID;
NSString * speakerName;
NSString * speakerPosition;
NSString * speakerCompany;
} SpeakerEntry;
static const SpeakerEntry speakerTable [] =
{
{0, #"name", #"position", #"company"},
{1, #"name", #"position", #"company"},
{-1, nil, nil, nil}
};
This works correctly...
-(NSString *) stringSpeakerCompanyForId:(NSUInteger) identifier{
NSString * returnString = nil;
if ([self helpCount] > identifier) {
returnString = speakerTable[identifier].speakerCompany;
}
return returnString;
}
This does not work at all..
-(id) getSpeaker{
//if ([speakerTable[0].speakerName isKindOfClass:[NSString class]])
// NSLog(#"YES");
NSArray * myArray3 = [NSArray arrayWithArray:speakerTable];
return myArray3;
}
arrayWithArray expects an NSArray, not a C array.
The first one works because you are using it like a C array.
Alternatively - don't use a struct, use an object instead:
Create a class called Speaker.
In Speaker.h
#interface Speaker : NSObject {}
#property (nonatomic, assign) NSUinteger id;
#property (nonatomic, copy) NSString name;
#property (nonatomic, copy) NSString position;
#property (nonatomic, copy) NSString company;
- (void)initWithId:(NSUInteger)anId name:(NSString *)aName position:(NSString *)aPosition company:(NSString *)aCompany;
#end
in Speaker.m
#import "Speaker.h"
#implementation Speaker
#synthesize id, name, position, company;
- (void)initWithId:(NSUInteger)anId name:(NSString *)aName position:(NSString *)aPosition company:(NSString *)aCompany {
if (!([super init])) {
return nil;
}
id = anId;
NSString name = [[NSString alloc] initWithString:aName];
NSString position = [[NSString alloc] initWithString:aPosition];
NSString company = [[NSString alloc] initWithString:aCompany];
return self;
}
- (void)dealloc {
[name release];
[position release];
[company release];
[super dealloc];
}
#end
And now in your calling code you can create an immutable array of speakers with:
Speaker *speaker0 = [[Speaker alloc] initWithId:0 name:#"name0" position:#"position0" company:#"company0"];
Speaker *speaker1 = [[Speaker alloc] initWithId:1 name:#"name1" position:#"position1" company:#"company1"];
Speaker *speakerNull = [[Speaker alloc] initWithId:-1 name:nil position:nil company:nil];
NSArray *speakerArray [[NSArray arrayWithObjects: speaker0, speaker1, speakerNull] retain]
[speaker0 release];
[speaker1 release];
[speakerNull release];
note: this is typed straight in, so feel free to mention/correct typos or errors
The method arrayWithArray takes in an NSArray as an argument, not a C array.

Why is only one attribute of my <NSCoding> object being properly written to a file?

So I'm trying to write a NSMutableArray of custom objects (a "Course" representing a college course for a Course Planner app) to a file when my application terminates and then read that array from the file into the relevant ViewController that will make use of the data when the application starts up.
Here is the relevant code:
CoursesAppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
coursesViewController = [[SampleHomeScreen alloc] initWithNibName:#"SampleHomeScreen" bundle:nil];
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
[coursesViewController setCourses:[NSKeyedUnarchiver unarchiveObjectWithFile: filePath]];
}
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationWillTerminate:)name:UIApplicationWillTerminateNotification object:app];
[window addSubview:coursesViewController.view];
[window makeKeyAndVisible];
return YES;
}
- (NSString *)dataFilePath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:#"data.plist"];
NSLog(#"%#", path);
return path;
}
/**
applicationWillTerminate: saves changes in the application's managed object context before the application terminates.
*/
- (void)applicationWillTerminate:(UIApplication *)application {
NSLog(#"%#", [coursesViewController courses]);
[NSKeyedArchiver archiveRootObject:[coursesViewController courses] toFile:[self dataFilePath]];
}
Course.h:
#interface Course : NSObject <NSCoding> {
NSString *name; //e.g. ECS 189H
double grade, totalWeight; //course grade in %
NSMutableArray *list;
}
#property (nonatomic, retain) NSString *name;
#property (nonatomic) double grade, totalWeight;
#property (nonatomic, retain) NSMutableArray *list;
-(Course *)initWithName:(NSString *)courseName;
#end
Course.m:
#implementation Course
#synthesize name, grade, totalWeight, list;
-(Course *)initWithName:(NSString *)courseName {
name = [courseName retain];
grade = -1.0;
totalWeight = 0.0;
list = [[NSMutableArray alloc] init];
[super init];
return self;
}
-(Course *)initWithCoder:(NSCoder *)aDecoder {
self.name = [aDecoder decodeObjectForKey:#"name"];
self.grade = [aDecoder decodeDoubleForKey:#"grade"];
self.totalWeight = [aDecoder decodeDoubleForKey:#"totalWeight"];
self.list = [aDecoder decodeObjectForKey:#"list"];
[super init];
return self;
}
- (void) encodeWithCoder: (NSCoder *)coder
{
[coder encodeObject:name forKey:#"name"];
[coder encodeDouble:grade forKey:#"grade"];
[coder encodeDouble:totalWeight forKey:#"totalWeight"];
[coder encodeObject:list forKey:#"list"];
}
-(void)dealloc {
[name release];
[list release];
[super dealloc];
}
#end
[coursesViewController courses] is the NSMutableArray that holds the course objects. I know for a fact that it holds valid data.
So the problems are,
1: The application saves to data.plist ONLY when I run it from xcode (ie click "compile and run" in xcode).
2: It loads data from the plist, but all that gets saved are the course names and the default values for grade and totalWeight (-1 and 0 respectively). So really they are saved as though initWithName was called on them first.
This is my first real delve into a fairly advanced iOS application, so as I am a newbie to this, I may have left out some important info. If that is the case, please let me know and I will update the question.
Thanks!
-HT
p.s. If it is relevant, I have doNotRunInBackground in the info.plist set to true.
Your are trying to set values in your object before it's been initialized. And initialization will then reset your values.
-(Course *)initWithName:(NSString *)courseName {
name = [courseName retain]; // <- Accessing ivar before self is initialized
grade = -1.0; // <- Accessing ivar before self is initialized
totalWeight = 0.0; // <- Accessing ivar before self is initialized
list = [[NSMutableArray alloc] init]; // <- Accessing ivar before self is initialized
[super init]; // initialization resets your values !!!!
return self;
}
Additionally you are ignoring super's init return value, which will work fine 98 % of all cases, but I recommend to always use a proper initialization scheme:
- (id)init {
if (self = [super init]) {
// It's save to access ivars here
}
return self
}
In Cocoa an init method may return a different object, then the one that was allocated. So you must assign self to the super's init.
So, your init should look like:
-(Course *)initWithName:(NSString *)courseName {
if (self = [super init]) {
name = [courseName retain];
grade = -1.0;
totalWeight = 0.0;
list = [[NSMutableArray alloc] init];
}
return self;
}
The same applies to initWithCoder:(NSCoder *)coder.