 |
 |
NSMutableArray: Saving Data Question
|
 |
|
 |
|
Mac Enthusiast
Join Date: Feb 2003
Location: Portland, Oregon
Status:
Offline
|
|
Okay please bare with me here. I'm a CS student but I'm trying to teach myself Cocoa via a couple of O'Reilly books, so be gental
I'm creating an app that will take user input from some text fields, and then display that data in a table. Okay thats all good, done deal, and the saving / loading of that data works just fine.
Now in a different file, I want to take input from text fields as above, but then instead of displaying that information in a table and saving it via the table, I want to save the data directly to file.
Now the code I'm using for translating the user input data from text fields to data is:
- (NSDictionary *)createRecord
{
/* Load data from the vehicle input fields into the table */
NSMutableDictionary *record = [[NSMutableDictionary alloc] init];
[record setObject:[inMake stringValue] forKey:@"make"];
[record setObject:[inDate stringValue] forKey:@"date"];
[record setObject:[inMiles stringValue] forKey:@"miles"];
[record setObject:[inModel stringValue] forKey:@"model"];
[record setObject:[inWho stringValue] forKey:@"purchasee"];
[record setObject:[inYear stringValue] forKey:@"year"];
[record autorelease];
return record;
}
The XML output from this is very nicely structured in that it has the forKey name, and the value next to it. This is exactly what I want.
Now for the second file, where I'm taking input from text fields, building an array, and then saving it directly, the code is:
- (IBAction)addMaintenance  id)sender
{
/* Initialize Array with the contents of the old array for pending */
recordsFile = @"~/Desktop/MaintenanceRecordsOther.plist";
recordsFile = [recordsFile stringByExpandingTildeInPath];
[recordsFile retain];
addMain = [[NSMutableArray alloc] initWithContentsOfFile:recordsFile];
/* Build Array */
[addMain addObject:[inDateM stringValue]];
[addMain addObject:[inCostM stringValue]];
[addMain addObject:[inWhoM stringValue]];
[addMain addObject:[inMilesM stringValue]];
/* Save Array */
[addMain writeToFile:recordsFile atomically:YES];
}
Now the saved output for this file sucks. All it has is 1 through oo and then the data value next to it. This is not what I want. The difference, wrather, the major difference here is that the first file is being built using an NSMutableDictionary, and the second NSMutableArray. I don't think I can use an NSMutableDictionary for the second file because the data has no "forKey" value since there is no table interaction.
So my question is, is it possible for me to get some formatting going for my second file? In that I would like say:
Date 9/9/9
Time 10:10
instead of:
1 9/9/9
2 10:10
Understand?
I hope I"m making since. It's hard for me to explain something I don't entirely understand.
jesse ;-)
|
|
|
| |
|
|
|
 |
|
 |
|
Forum Regular
Join Date: Jun 2001
Location: Savoy, IL USA
Status:
Offline
|
|
Can you shed a little more light on what the maintenance records look like? Is it possible that what you want is a list of discrete records, with each containing the "date", "cost", "who" and "miles" information?
If this is true, you can use an array of dictionaries:
Code:
- (IBAction)addMaintenance: (id)sender
{
// Initialize Array of records with the contents of the old array,
// which we will append to
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
recordsFile = @"~/Desktop/MaintenanceRecordsOther.plist";
recordsFile = [recordsFile stringByExpandingTildeInPath];
[recordsFile retain];
addMain = [[NSMutableArray alloc] initWithContentsOfFile:recordsFile];
// Build Record
// NOTE: Make absoultely sure that
// the stringValue method returns
// empty string (@"") and not nil, or this
// will throw an exception at run time!
// If it does return nil, just bracket each of
// these with a check
[dict setObject:[inDateM stringValue] forKey: @"maintDate"];
[dict setObject:[inCostM stringValue] forKey: @"maintCost"];
[dict setObject:[inWhoM stringValue] forKey: @"maintWho"];
[dict setObject:[inMilesM stringValue] forKey: @"maintMiles"];
// Add record to array
[addMain addObject: dict];
// Save Array
[addMain writeToFile:recordsFile atomically:YES];
}
You get the basic idea here: you create a record nicely formatted for the record you wish to save, then you keep a list of the records (in the order they are appended to the list) and then dump the whole list to the file. As long as you only nest arrays, dictionarys, NSNumbers, NSData or strings (probably other types I'm missing as well), you can build rather complex container structures and save them all to an XML file. In fact, this is how NSUserDefaults works with all of the preferences that you can set up for an application (also something you may want to look into as a solution for these files, since you probably don't want to have to have all these things on your Desktop or anything).
Now, there are some other problems with this code, in terms of memory leaks and inefficient initialization. If you're not ready for those things yet, that's ok (just skip this part, then), but it's pretty important to develop good habits from the outset. So, let's take a closer look at your code:
NOTE: I'm making the assumption here that recordsFile and addMain are members of some controller class that is handling all this data. If not, then you're example code isn't correct, since they won't have been declared otherwise.
Code:
- (IBAction)addMaintenance: (id)sender
{
// Initialize Array of records with the contents of the old array,
// which we will append to
// Using the class method +dictionary here creates an autoreleased
// object that you won't need to explicitly -release later on
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
// the old way (without the if test) meant that recordsFile would get initialized &
// retained every time that addMaintenance was being called. This is a slow
// memory leak, and not very large, but still undesirable
if (recordsFile == nil)
{
recordsFile = @"~/Desktop/MaintenanceRecordsOther.plist";
recordsFile = [recordsFile stringByExpandingTildeInPath];
[recordsFile retain]; // This needs to be -released in -dealloc!
}
// Same deal here; alloc'ing & init'ing an object bumps the retain
// count to 1, so it needs to be initialized only once, and needs to
// be released in -dealloc
if (addMain == nil)
{
addMain = [[NSMutableArray alloc] initWithContentsOfFile:recordsFile];
}
// Build Record
[dict setObject:[inDateM stringValue] forKey: @"maintDate"];
[dict setObject:[inCostM stringValue] forKey: @"maintCost"];
[dict setObject:[inWhoM stringValue] forKey: @"whoMaint"];
[dict setObject:[inMilesM stringValue] forKey: @"milesMaint"];
// Add record to array
[addMain addObject: dict];
// Save Array
[addMain writeToFile:recordsFile atomically:YES];
}
Of course, the addMain leak is a potentially much bigger memory leak, but both aren't necessary. In short, while I haven't compiled this code, it should work and be a bit better than before.  Let me know if you have trouble with anything I've said here... 
|
Software Architect, CodeTek Studios, Inc.
12" AlBook 867 (Combo drive) 640 MB/40 GB (work development machine) -- TiBook 400MHz/384MB/10GB (home machine)
CodeTek VirtualDesktop Pro: Power multitasking! -- DockExtender: Powerful, efficient launcher for Apps, Docs and everything else!
|
| |
|
|
|
 |
|
 |
|
Mac Enthusiast
Join Date: Feb 2003
Location: Portland, Oregon
Status:
Offline
|
|
Perfect. A lot of great information here, I appreciate it. Your solution for the file structure of:
[dict setObject:[inDateM stringValue] forKey: @"maintDate"];
[dict setObject:[inCostM stringValue] forKey: @"maintCost"];
[dict setObject:[inWhoM stringValue] forKey: @"whoMaint"];
[dict setObject:[inMilesM stringValue] forKey: @"milesMaint"];
Is great, and indeed what I'm using for the table part of the code. My question is however, what "forKey" am I able to use for NSTextField input fields? I'm not sure how to set a key for those. Is their tag sufficient? For example: Say I have in input field (NSTextField) named inDate and I want to use it in conjunction with other input fields to build my mutable dictionary. And say I set the tag to 100, than could I say:
[dict setObject:[inDate stringValue] forKey:100];
When I was using the table, I was able to set the key value of each column, and then use that for the forKey, but I see now Key option for NSTextView.
jesse ;-)
|
|
|
| |
|
|
|
 |
|
 |
|
Forum Regular
Join Date: Jun 2001
Location: Savoy, IL USA
Status:
Offline
|
|
Originally posted by jessejlt:
Perfect. A lot of great information here, I appreciate it. Your solution for the file structure of:
[dict setObject:[inDateM stringValue] forKey: @"maintDate"];
[dict setObject:[inCostM stringValue] forKey: @"maintCost"];
[dict setObject:[inWhoM stringValue] forKey: @"whoMaint"];
[dict setObject:[inMilesM stringValue] forKey: @"milesMaint"];
Is great, and indeed what I'm using for the table part of the code. My question is however, what "forKey" am I able to use for NSTextField input fields? I'm not sure how to set a key for those. Is their tag sufficient? For example: Say I have in input field (NSTextField) named inDate and I want to use it in conjunction with other input fields to build my mutable dictionary. And say I set the tag to 100, than could I say:
[dict setObject:[inDate stringValue] forKey:100];
When I was using the table, I was able to set the key value of each column, and then use that for the forKey, but I see now Key option for NSTextView.
jesse ;-)
Well, I'm not entirely sure about your needs, but you could just #define some constants that are logically tied with each field:
Code:
#define MAINT_DATE_FIELD_KEY @"maintDate"
#define MAINT_COST_FIELD_KEY @"maintCost"
#define MAINT_WHO_FIELD_KEY @"maintCost"
#define MAINT_MILES_FIELD_KEY @"maintCost"
- (IBAction)addMaintenance: (id)sender
{
// Stuff above has been snipped out...
[dict setObject:[inDateM stringValue] forKey: @"maintDate"];
[dict setObject:[inCostM stringValue] forKey: @"maintCost"];
[dict setObject:[inWhoM stringValue] forKey: @"whoMaint"];
[dict setObject:[inMilesM stringValue] forKey: @"milesMaint"];
// Stuff below snipped out...
}
- (void) populateFieldsFromRecord: (NSDictionary *) record
{
// Just making up names for your NSTextField members,
// since you didn't say what you had named them
[maintDateField setStringValue: [record objectForKey: MAINT_DATE_FIELD_KEY]];
[maintCostField setStringValue: [record objectForKey: MAINT_COST_FIELD_KEY]];
[maintWhoField setStringValue: [record objectForKey: MAINT_WHO_FIELD_KEY]];
[maintMilesField setStringValue: [record objectForKey: MAINT_MILES_FIELD_KEY]];
}
You don't need to have a particular value for the key; it can be arbitrary, as long as you know what it is, and it doesn't collide with any other key. That's why I recommend #defining a constant (better yet would be a static NSString at the top of the .m file) that you're going to use as the key so you can use the defined symbol for setting or retrieving the info from the dictionary, and you can change the actual value without impacting your code anywhere.
Any questions about this? The important thing is that the key value for each entry can be anything you want it to be, as long as it's a string. You could use the tag for the field, but you have to make it a string, first. But, I wouldn't recommend hard-coding it into the setting/getting method calls (though I've been known to do that, too. BAD PROGRAMMING PRACTICE ALERT!  ).
|
Software Architect, CodeTek Studios, Inc.
12" AlBook 867 (Combo drive) 640 MB/40 GB (work development machine) -- TiBook 400MHz/384MB/10GB (home machine)
CodeTek VirtualDesktop Pro: Power multitasking! -- DockExtender: Powerful, efficient launcher for Apps, Docs and everything else!
|
| |
|
|
|
 |
 |
|
 |
|
|
|
|
|

|
|
 |
Forum Rules
|
 |
 |
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
|
HTML code is Off
|
|
|
|
|
|
 |
 |
 |
 |
|
 |
|