Unable to save NSMutableArray of my class to file (IOS) - objective-c

Can't find out why my code doesn't work. Please help someone.
I created my own class, implemented NSCoding protocol. Do not know what i miss or wrong.
Here is saving code
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fileName = [documentsDirectory stringByAppendingPathComponent:#"Currency.plist"];
Item * item = [[Item alloc] init];
item.x = 3; item.y = 5; item.type = (TType) 3; item.isSelected = NO;
NSMutableArray * array = [[NSMutableArray alloc] init];
[array addObject:item];
[array fileName atomically:YES] // ( Doesn't Save the file ,returns NO);
Here is the code of my class
*.h*
#import <Foundation/Foundation.h>
enum TType
{
kNone = 0,
KFirst = 1,
....
};
#interface Item : NSObject <NSCoding>{
}
#property (nonatomic) int x;
#property (nonatomic) int y;
#property (nonatomic) enum TType type;
#property (nonatomic) BOOL isSelected;
#end
.m
#implementation Item
#synthesize x, y , type , isSelected;
#pragma mark NSCoding Protocol
- (void)encodeWithCoder:(NSCoder *)encoder;
{
[encoder encodeInt32:[self x] forKey:#"x"];
[encoder encodeInt32:[self y] forKey:#"y"];
[encoder encodeInt32:[self type] forKey:#"type"];
[encoder encodeBool:[self isSelected] forKey:#"isSelected"];
}
- (id)initWithCoder:(NSCoder *)decoder;
{
if ( ![super init] )
return nil;
[self setX:[decoder decodeInt32ForKey:#"x"]];
[self setY:[decoder decodeInt32ForKey:#"y"]];
[self setType:(TType)[decoder decodeInt32ForKey:#"color"]];
[self setIsSelected:[decoder decodeBoolForKey:#"isSelected"]];
return self;
}
#end

I think you'll find your answer at: objects conforming to nscoding will not writetofile
i.e. you can't serialize your Item class to a property list, since it isn't a property list object (NSString, NSData, NSArray, or NSDictionary).
See the documentation for writeToFile:atomically::
This method recursively validates that all the contained objects are property list objects before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.

Related

Objective-C addObject seems to put object into all array indices

I am trying to learn how to make simple classes.
So far I am not getting the results expected using addObject and my class.
Here is what I have:
In my view controller:
#import "onoffclass.h"
In its viewDidLoad:
NSMutableArray *inTable;
onoffclass *therec;
onoffclass *readrec;
inTable = [NSMutableArray array];
therec = [[onoffclass alloc]init];
readrec = [[onoffclass alloc]init];
for (int lop=0;lop<3;lop++){
therec.parsedID = [NSString stringWithFormat:#"%i",lop];
[inTable addObject:therec];
NSLog(#"lop=%i onoff.parsedID=%#",lop,therec.parsedID);
for (int z=0;z<[inTable count];z++){
readrec = inTable[z];
NSLog(#" inTable[%i] parsedID=%#",z,readrec.parsedID);
}
}
In my onoffclass.h:
#interface onoffclass : NSObject
#property NSString *parsedID;
#property NSString *parsedOn;
#property NSString *parsedOff;
#property NSString *parsedAdj;
#property NSString *parsedRoom;
#property NSString *parsedBuilding;
#property NSString *parsedWho;
#property NSString *parsedInfo;
#property NSString *parsedBillable;
-(onoffclass*)initWithSomeString: (NSString*)blah AndSomeNum: (int)num;
-(NSString*)description;
#end
In my onoffclass.m:
#import <Foundation/Foundation.h>
#import "onoffclass.h"
#implementation onoffclass {
NSString *_parsedID;
NSString *_parsedOn;
NSString *_parsedOff;
NSString *_parsedAdj;
NSString *_parsedRoom;
NSString *_parsedBuilding;
NSString *_parsedWho;
NSString *_parsedInfo;
NSString *_parsedBillable;
}
-(onoffclass*)initWithSomeString: (NSString*)blah AndSomeNum: (int)num {
self = [super init];
_parsedID = blah;
_parsedOn = #"on";
_parsedOff = #"off";
_parsedAdj = #"adj";
_parsedRoom = #"room";
_parsedBuilding = #"building";
_parsedWho = #"who";
_parsedInfo = #"info";
_parsedBillable = #"billable";
return self;
}
-(NSString*)description {
return [NSString stringWithFormat: #"%#", _parsedID];
}
#end
Here is the output:
lop=0 onoff.parsedID=0
inTable[0] parsedID=0
lop=1 onoff.parsedID=1
inTable[0] parsedID=1
inTable[1] parsedID=1
lop=2 onoff.parsedID=2
inTable[0] parsedID=2
inTable[1] parsedID=2
inTable[2] parsedID=2
Why does it appears that addObject is updating all array indices and how do I fix this?
Thanks,
Dale
You are creating one instance of onoffclass (by the way class names should start with a capital letter) then you are adding the same instance again and again in the loop. Since classes are reference types changing a property affects all occurrences of the same instance.
Solution is to put the line to create an instance in the loop
for (int lop = 0; lop < 3; lop++) {
therec = [[onoffclass alloc] init];
...

Write a complex array of custom structs to file Objective C

I need to save and load the contents of an array of structs, but I know that Objective C is very particular about which data types you can read/write with.
Here is my struct:
struct SCourse
{
NSMutableArray* holes; // holds integers (pars)
NSString* name;
int size;
BOOL inUse;
};
#interface CoursesManager : NSObject
{
struct SCourse courses[5];
}
What are the data types I'll need to use? Do they each have different methods needed in order to read/write? I'm just looking for a non-complex way to get all the data I need to and from a file. I could do this quite easily in a language I'm more familiar with (C++), but some of the particulars of Objective-c are still lost on me.
EDIT: Solution (thanks for the help, everyone)
-(void)applicationWillResignActive:(UIApplication *)application {
// save the courses
NSMutableArray* totalWriteArray = [[NSMutableArray alloc] initWithCapacity:MAX_COURSES];
for (int i = 0; i < MAX_COURSES; ++i)
{
struct SCourse saveCourse = [coursesManager GetCourseAtIndex:i];
NSNumber* nInUse = [NSNumber numberWithBool:saveCourse.inUse];
NSNumber* nSize = [NSNumber numberWithInt:saveCourse.size];
NSMutableArray* writeArray = [[NSMutableArray alloc] initWithCapacity:4];
[writeArray addObject:nInUse];
[writeArray addObject:nSize];
[writeArray addObject:saveCourse.name];
[writeArray addObject:saveCourse.holes];
[totalWriteArray addObject:writeArray];
}
[totalWriteArray writeToFile:[self saveFilePath] atomically:YES];
}
And for the loading back in...
-(void)loadFile {
NSString *myPath = [self saveFilePath];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:myPath];
if (fileExists) {
NSMutableArray* totalReadArray = [[NSMutableArray alloc] initWithContentsOfFile:[self saveFilePath]];
for (int i = 0; i < MAX_COURSES; ++i)
{
struct SCourse loadCourse = [coursesManager GetCourseAtIndex:i];
NSMutableArray* loadArray = [totalReadArray objectAtIndex:i];
NSNumber* nInUse = [loadArray objectAtIndex:0];
loadCourse.inUse = [nInUse boolValue];
NSNumber* nSize = [loadArray objectAtIndex:1];
loadCourse.size = [nSize integerValue];
NSString* inName = [loadArray objectAtIndex:2];
loadCourse.name = inName;
NSMutableArray* inHoles = [loadArray objectAtIndex:3];
loadCourse.holes = inHoles;
[coursesManager ReplaceCourseAtIndex:i With:loadCourse];
}
}
}
First thing first. You shouldn't use plain old C structures. The ARC memory management will not appreciate.
If you are familiar with C++, you should maybe use a C++ class instead, which will please the compiler and runtime. Depends on what you want to do.
Array. Use either NSArray or std::vector but please, no plain C arrays. Not sure how ARC will handle this but I suppose it will not appreciate much. Objective-C and C++ both provides all the tools you need to handle collections of whatever.
Serialization. You have several possibilities, one of them is NSCoder.
Last word, with the so called modern syntax, converting things into ObjC objects is quite easy.
BOOL b = YES;
int i = 10;
double d = 3.14;
char* s = "Pouf pouf";
You get the ObjC equivalents with the boxin' thingy:
NSNumber* bo = #( b );
NSNumber* io = #( i );
NSNumber* do = #( d );
NSString* so = #( s );
NSArray* ao = #[ #( i ), do ];
NSDictionary* = #{ #"num" : io, #"str" : #( s ) };
To write something in a file, in one gracious step:
[#{ #"bool" : bo, #"array" : #[ #"string", #10, #( 10 + 20 ) ] }
writeToFile: #"path.plist" atomically: YES];
But the question remains, what are you trying to accomplish?
One easy approach is to store these arrays in an NSMutableDictionary object and use the method:
[mutableDict writeToFile:#"path/to/file" atomically:YES];
To store the data and:
NSMutableDictionary *anotherDict = [NSMutableDictionary dictionaryWithContentsOfFile:#"path/to/file"];
To read the contents back in.
Here's what I'd suggest:
Make a custom class with the properties you want (.h file):
#import <Foundation/Foundation.h>
#interface CustomHolder : NSObject {
NSString *last;
NSString *first;
NSString *middle;
}
#property (strong, nonatomic) NSString *last;
#property (strong, nonatomic) NSString *first;
#property (strong, nonatomic) NSString *middle;
#end
And then set the .m file up so that you can encode/decode the object
#import "CustomHolder.h"
#implementation CustomHolder
#synthesize last, first, middle;
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:first forKey:#"first"];
[encoder encodeObject:last forKey:#"last"];
[encoder encodeObject:middle forKey:#"middle"];
}
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super init])
{
self.first = [decoder decodeObjectForKey:#"first"];
self.last = [decoder decodeObjectForKey:#"last"];
self.middle = [decoder decodeObjectForKey:#"middle"];
}
return self;
}
#end
Then you can just
[NSKeyedArchiver archiveRootObject:obj toFile:[self saveFilePath]] to save and
[NSKeyedUnarchiver unarchiveObjectWithFile:[self saveFilePath]] to load
That's probably the most similar to using C-structs (especially because ARC doesn't let you use structs).

Can't save to plist

I have my own object class from inherited from NSObject
#interface BlockedCell : NSObject
{
NSValue *gridValue;
NSString *name;
}
#property (nonatomic, retain) NSValue *gridValue;
#property (nonatomic, retain) NSString *name;
#end
So I try to create a few objects:
NSMutableDictionary *dict = [[dict alloc] init];
for (int i = 0; i < 5; ++i)
{
BlockedCell *block = [[BlockedCell alloc] init];
block.gridValue = [NSValue valueWithCGPoint: CGPointMake(0.0f, 0.0f)];
block.name = #"something";
[dict setObject: block forKey: [NSString stringWithFormat: #"item_%d", i]];
[block release];
}
if([dict writeToFile: path atomic: YES])
NSLog(#"Saved");
else
NSLog(#"Failed to save");
[dict release];
And what I get for the output is "Failed to save"..
If my dictionary does not contains any data, then it will output "Saved"
EDIT:
After I did more testing, I found out that actually is the NSValue causing the saving failed.
So what should I do if I want to save CGPoint into plist?
As you discovered, property lists cannot store NSValue objects directly. The supported classes are NSData, NSString, NSArray, NSDictionary, NSDate, and NSNumber, as documented in the NSPropertyListSerialization Class Reference.
The easiest workaround would be to use NSString instead of NSValue:
block.gridString = NSStringFromCGPoint(CGPointZero);
CGPoint point = CGPointFromString(block.gridString);
you cant save NSValue directly.In your case you have to save a point in the form ofstring use below line
CGPoint point = CGPointMake(10.0,20.0)
//you need to translate the point into a compatible "Plist" object such as NSString
//luckily, there is a method for that
[rectArray addObject:NSStringFromPoint(point)];
//save into a plist
.....
on retrieval of this value
CGPoint Point = CGPointFromString([rectArray objectAtIndex:0]);

Issue with save file to Documents NSMutableArray with NSObjects

I'm a newbie iOS developer.
I wrote a small application that save an NSMutableArray array with my objects that derived from NSObject.
Application do the save but the file isn't created in document directory and application can't read.
this issue is both on the simulator and my iPhone 3gs 4.2.1
My NSMutableArray definition inside the appDelegate class:
#property (nonatomic,retain, readwrite) NSMutableArray *places;
My NSObject class:
#import <Foundation/Foundation.h>
#interface Place : NSObject {
NSString *name;
NSString *location;
}
-(id) init:(NSString *)name: (NSString *)location;
#property (retain,nonatomic,readwrite) NSString *name;
#property (retain,nonatomic,readwrite) NSString *location;
#end
My StorageService library class:
#import "StorageService.h"
#implementation StorageService
-(id) init {
self = [super init];
if (self != nil) {
}
return self;
}
-(void) saveArrayToFile:(NSString*) filename : (NSMutableArray *)arrayToSave{
// get full path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *fullPath = [paths objectAtIndex:0];
fullPath = [fullPath stringByAppendingPathComponent:filename];
NSLog(#"Save in %#",fullPath);
[arrayToSave writeToFile:fullPath atomically:YES];
}
-(NSMutableArray*) readArrayFromFile:(NSString *)filename {
// get full path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *fullPath = [paths objectAtIndex:0];
fullPath = [fullPath stringByAppendingPathComponent:filename];
if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath]) {
NSMutableArray *data = [[NSMutableArray alloc] initWithContentsOfFile:fullPath];
if (data == nil) {
data = [[NSMutableArray alloc] init];
}
NSLog(#"Read from %#",fullPath);
return data;
} else {
NSMutableArray *data = [[NSMutableArray alloc] initWithContentsOfFile:fullPath];
return data;
}
}
-(void) dealloc {
[super dealloc];
}
#end
and My functions in the appDelegate:
-(void) saveApplicationData {
[self.storageService saveArrayToFile : PLACES_FILE : self.places];
}
-(void) loadApplicationData {
self.places = [self.storageService readArrayFromFile:PLACES_FILE];
}
Here is my class that holds constant to filename:
#import <Foundation/Foundation.h>
extern NSString * const PLACES_FILE = #"Places.dat";
#interface ApplicationConstants : NSObject {
}
#end
So what is wrong?
Thank you guys.
What you want is to let Place conform to the NSCoding protocol, to allow for serialization to and from files (and in memory data if wanted)
Extend Place as (I have also changed the name of the init method as your name was against every naming practice iOS has):
#interface Place : NSObject <NSCoding> {
NSString *name;
NSString *location;
}
-(id)initWithName:(NSString *)name location:(NSString *)location;
#property (retain,nonatomic,readwrite) NSString *name;
#property (retain,nonatomic,readwrite) NSString *location;
#end
Your implementation is quite simple but you also need to implement two methods defined by the NSCoding protocol:
#implementation Place
#synthesize name, location;
-(id)initWithName:(NSString *)aName location:(NSString *)aLocation {
self = [super init];
if (self) {
self.name = aName;
self.location = aLocation;
}
return self;
}
-(id)initWithWithCoder:(NSCoder)decoder {
self = [super initWithCoder:decoder];
if (self) {
self.name = [decoder decodeObjectForKey:#"name"];
self.location = [decoder decodeObjectForKey:#"location";
}
return self;
}
-(void)encodeWithCoder:(NSCoder*)encoder {
[encoder encodeObject:self.name forKey:#"name"];
[encoder encodeObject:self.location forKey:#"location"];
[super encodeWithCoder:encoder];
}
#end
With this in place, saving the places array to disk is as easy as:
[NSKeyedArchiver archiveRootObject:places toFile:path];
And decoding just as easy:
places = [[KSKeyUnarchiver unarchiveObjectWithFile:path] retain];
To use writeToFile objects in array need to be plist capable type (NSDate, NSDate, NSString, NSArray, NSDictionary)
Implement NSCoding on the objects in array and use NSKeyedArchiver to serialize/deserialize.
write:
[NSKeyedArchiver archiveRootObject:myArray toFile:self.places];
read:
[NSKeyedUnarchiver unarchiveObjectWithFile:path];
More info can be found here:
Persisting Custom Objects

Objective-C for Dummies: How do I loop through an NSDictionary inside of an NSDictionary?

Alright guys, I'm quite confused. So, I have an NSDictionary which is populated by a JSON string which looks like:
{"Success":true,"Devices":[{"UDId":"...","User":"...","Latitude":0.0,"Longitude":0.0}]}
Now, I know how to check if Success is true, but I need to loop through the array of Devices (JSON object) and create an internal array of Devices (internal app object) and I have no idea how to do that. Can someone please explain how to do it?
Here's my Device.m/h:
#import <CoreLocation/CoreLocation.h>
#import <Foundation/Foundation.h>
#interface Device : NSObject {
NSString *udId;
NSString *name;
NSNumber *latitude;
NSNumber *longitude;
}
#property (nonatomic, retain) NSString *udId;
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSNumber *latitude;
#property (nonatomic, retain) NSNumber *longitude;
#pragma mark -
#pragma mark MKAnnotation Properties
#property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
#end
----
#import "Device.h"
#implementation Device
#synthesize udId, name, latitude, longitude;
- (CLLocationCoordinate2D)coordinate {
CLLocationCoordinate2D internalCoordinate;
internalCoordinate.latitude = [self.latitude doubleValue];
internalCoordinate.longitude = [self.longitude doubleValue];
return internalCoordinate;
}
- (void)dealloc {
[udId release];
udId = nil;
[name release];
name = nil;
[latitude release];
latitude = nil;
[longitude release];
longitude = nil;
[super dealloc];
}
#end
And here's the methods where I should be reading the response and converting it to objects I can use:
- (void)requestFinished:(ASIHTTPRequest *)request {
if (![request error]) {
NSError *jsonError = nil;
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithJSONString:[request responseString] error:&jsonError];
if (!jsonError || ([[jsonDictionary objectForKey:#"Success"] intValue] == 1)) {
// READ "DEVICES" AND CONVERT TO OBJECTS
} else {
// AUTHORIZATION FAILED
}
}
}
I'd really appreciate some help on this. I just can't seem to wrap my head around it...
Thanks in advance!
You are almost there. In your code where you say:
// READ "DEVICES" AND CONVERT TO OBJECTS
do this:
NSArray * devices = [jsonDictionary objectForKey:#"Devices"];
for(NSDictionary * deviceInfo in devices) {
Device * d = [[[Device alloc] init] autorelease];
[d setLatitude:[deviceInfo objectForKey:#"Latitude"]];
[d setLongitude:[deviceInfo objectForKey:#"Longitude"]];
[d setName:[deviceInfo objectForKey:#"User"]];
[d setUdId:[deviceInfo objectForKey:#"UDId"]];
// do some stuff with d
}
What's going on here: I didn't see what JSON library you are using to convert, but presuming it works like TouchJSON or SBJSON, the JSON array is automatically turned into an NSArray instance, while the inner hashes of the NSArray are NSDictionary objects. At the point that you have deserialized that JSON string, everything you're dealing with will be instances of NSString, NSNumber, NSArray and NSDictionary (and depending on the library, NSNull to represent null values).
First you need to define your initializer/constructor for your Device class.
Device.h
- (id)initWithUdid:(NSString *)udid name:(NSString *)name latitude:(NSNumber *)lat longitude:(NSNumber *)lon;
Device.m
- (id)initWithUdid:(NSString *)udid name:(NSString *)name latitude:(NSNumber *)lat longitude:(NSNumber *)lon {
if (self = [super init]) {
self.udid = udid;
self.name = name;
self.latitude = lat;
self.longitude = lon;
}
return self;
}
Then you can initialize a new object like:
Device *dev = [[Device alloc] initWithUdid:#"a udid" name:#"the name" latitude:latNum longitude:lonNum];
So, you should be able to iterate the array and build your Device objects like so:
NSArray *devicesArray = [dict objectForKey:#"Devices"];
for (NSDictionary *d in devicesArray) {
Device *dev = [[Device alloc] initWithUdid:[d objectForKey:#"UDId"]
name:[d objectForKey:#"User"]
latitude:[NSNumber numberWithDouble:[d objectForKey:#"Latitude"]]
longitude:[NSNumber numberWithDouble:[d objectForKey:#"Latitude"]]];
}
You want to access the array of device dictionaries from the top-level dictionary just as you did the Success value. Then iterating over the dictionaries you can use each's -keyEnumerator method to iterate over its keys.
- (void)requestFinished:(ASIHTTPRequest *)request {
if (![request error]) {
NSError *jsonError = nil;
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithJSONString:[request responseString] error:&jsonError];
if (!jsonError || ([[jsonDictionary objectForKey:#"Success"] intValue] == 1)) {
NSArray* deviceArray = [jsonDictionary objectForKey:#"Devices"];
for(NSDictionary* dict in deviceArray)
{
for(NSString* key in [dict keyEnumerator])
{
NSLog(#"%# -> %#", key, [dict objectForKey:key]);
}
}
// READ "DEVICES" AND CONVERT TO OBJECTS
} else {
// AUTHORIZATION FAILED
}
}
}
Sounds like you need to reuse your line:
[jsonDictionary objectForKey:#"Success"]
try having a look at
[jsonDictionary objectForKey:#"Devices"]
You really need to figure out what type it returns.
If you're lucky, it returns an NSDictionary, or alternately something that you can easily turn into an NSDictionary.