Adding values into my plist - iphone

I have this plist that I have created
I have written most of my controller class which gets this plist and loads it into the documents directory so its possible to read/write to is.
Currently I have the reading working fine, and I used to have the writing working also, however I have just recently changed one of the objects (cache value) to a Dictionary with values related to that. Now when I try to write to this plist my app is crashing.
This is the error I am getting.
2012-04-05 09:26:18.600 mycodeTest[874:f803] * Terminating app due to
uncaught exception 'NSInvalidArgumentException', reason: '*
-[NSDictionary initWithObjects:forKeys:]: count of objects (4) differs from count of keys (5)'
*** First throw call stack: (0x12cc022 0x1884cd6 0x1248417 0x12685e2 0x19844 0x17e86 0x17669 0x13b67 0xe53a49 0xe51e84 0xe52ea7 0xe51e3f
0xe51fc5 0xd96f5a 0x1d2aa39 0x1df7596 0x1d21120 0x1df7117 0x1d20fbf
0x12a094f 0x1203b43 0x1203424 0x1202d84 0x1202c9b 0x21aa7d8 0x21aa88a
0x450626 0x77ed 0x1e35 0x1) terminate called throwing an
exceptionCurrent language: auto; currently objective-c
with all of this in mind I will now show you my method, which is called from another class when it has the values ready to be saved.
//This method gets called from another class when it has new values that need to be saved
- (void) saveData:(NSString *)methodName protocolSignature:(NSString *)pSignature protocolVersion:(NSNumber *)pVersion requestNumber:(NSNumber *)rNumber dataVersionReturned:(NSNumber *)dvReturned cacheValue:(NSMutableDictionary *)cValue
{
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"EngineProperties.plist"];
// set the variables to the values in the text fields that will be passed into the plist dictionary
self.protocol = pSignature;
self.Version = pVersion;
self.request = rNumber;
self.dataVersion = dvReturned;
//if statment for the different types of cacheValues
if (methodName == #"GetMan")
{
//cache value only returns the one cachevalue depending on which method name was used
[self.cacheValue setValue:cValue forKey:#"Man"]; //do I need to have the other values of cacheValue dictionary in here? if so how do I do that.
c
}
else if (methodName == #"GetMod")
{
[self.cacheValue setValue:cValue forKey:#"Mod"];
}
else if (methodName == #"GetSubs")
{
[self.cacheValue setValue:cValue forKey:#"Subs"];
}
// This is where my app is falling over and giving the error message
// create dictionary with values in UITextFields
NSDictionary *plistDict = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects: protocol, pVersion, rNumber, dvReturned, cacheValue, nil] forKeys:[NSArray arrayWithObjects: #"Signature", #"Version", #"Request", #"Data Version", #"Cache Value", nil]];
NSString *error = nil;
// create NSData from dictionary
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
// check is plistData exists
if(plistData)
{
// write plistData to our Data.plist file
[plistData writeToFile:plistPath atomically:YES];
NSString *myString = [[NSString alloc] initWithData:plistData encoding:NSUTF8StringEncoding];
NSLog(#"%#", myString);
}
else
{
NSLog(#"Error in saveData: %#", error);
// [error release];
}
}
I am abit lost when the error is saying that 4 keys differ from 5 when as far as i can tell i am applying 5 values to the dictionary any help would be appreciated.
Edit** another thing I noticed when debugging my issues was the fact it looks like I am not getting my cacheValue dictionary set up properly as its showing 0 key valuepairs??? is this right or wrong?
this is what happens when I log my plist in xcode as suggested below when I use [NSDictionary dictionaryWithObjectsAndKeys:..etc
Check setup is everything there?
Temp Dic output = {
Root = {
"Cache Value" = {
Manu = 0;
Mod = 0;
Sub = 0;
};
"Data Version returned" = 0;
"Signature" = null;
"Version" = 0;
"Request Number" = 0;
};
Run Man cache check results
Temp Dic output = {
"Version returned" = 5;
"Signature" = Happy;
"Version" = 1;
"Request Number" = 4;
as you can see Cache Value is completely missing after I have run the request.

I'm going to guess that cacheValue is nil when the crash occurs, resulting in only 4 objects in your values array, but 5 in keys.
Try using [NSDictionary dictionaryWithObjectsAndKeys:] instead.

In a situation like this, break up your code. Do each piece on a separate line, with temporary variables.
Put your keys and your values into temporary arrays.
Lot the values of everything, or set breakpoints in the debugger and examine all your values. Eli is almost certainly right that cacheValue is nil. The arrayWithObjects method stops on the first nil.
This code:
NSString *string1 = #"string 1";
NSString *string2 = #"string 2";
NSString *string3 = #"string 3";
NSString *string4 = nil;
NSString *string5 = #"string 5";
NSArray *anArray = [NSArray arrayWithObjects:
string1,
string2,
string3,
string4,
string5,
nil];
NSLog(#"anArray has %d elements", [anArray count]);
Will only show 3 elements in the array, even though the arrayWithObjects line appears to add 5 elements

Related

combining 2 strings in objective-c

I have a problem when I try to combine 2 NSString
I extract 2 NSSring form a JSON and its diagrams are:
thumbList: ( "picture1.jpg", "picture2.jpg", "picture3.jpg" ... )
fullnameList: ("name1", "name2" , "name3" ... )
My intention is unite them into one using the following scheme:
("name1", "picture1.jpg", "name2", "picture2.jpg", "name3", "picture3.jpg"...)
NSArray *array_webdata=[[NSArray array] init];
NSString *searchStatus = [[NSString alloc] initWithData:webData encoding:NSUTF8StringEncoding];
array_webdata = [parsedata objectWithString:searchStatus error:nil];
//String with all data of each user
NSString *usersList = [array_webdata valueForKey:#"results"];
NSLog(#"\n results? = %# \n", usersList);
//String with thumbs
NSString *thumbList = [usersList valueForKey:#"thumb"];
NSLog(#"\n thumbs? = %# \n", thumbList);
//String with usernames
NSString *fullnameList = [usersList valueForKey:#"fullname"];
NSLog(#"\n fullnames? = %# \n", fullnameList);
NSMutableIndexSet *indexes = [NSMutableIndexSet indexSetWithIndex:1];
[indexes addIndex:3];
[fullnameList insertObjects:thumbList atIndexes:indexes];
NSLog(#"array: %#", fullnameList);
But when I try to execute shows the next error message: [__NSArrayI insertObjects:atIndexes:]: unrecognized selector sent to instance.
Can anyone help me?
You should use
NSMutableDictionary* dataDict = [NSMutableDictionary dictionaryWithObjects:picturesList forKeys:namesList];
// Whenever key needed for fetching record from Dictionary just write
NSArray* keyArr = [dataDict AllKey];
Now you have all key and you can fetch record with the help of above key.
All "unrecognized selector sent to instance." errors mean the same: you think some object has a method, but it really don't have it at runtime.
Due to the dynamic nature of Objective-C, if you're not sure of some object having a method you should always test it calling respondsToSelector: like this:
if ([myObj respondsToSelector:#selector(someMethod)]) {
[myObj someMethod];
}
In this case,
NSString *fullnameList = [usersList valueForKey:#"fullname"];
is a NSString. That class does not have a insertObjects:atIndexes: method. Maybe you have to declare it as an NSMutableArray

How Do You DO A Plist Array Display with iPhone?

I have looked high and low for sample Plist display code and can't seem to find any that does what I want to do. I know it can't be too hard, but I am new to Plists.
I have the following Array in a Plist and I can't seem to get the right command to display the information. Am I doing something wrong in my Plist setup? It compiles fine but no Data gets to the iPhone Simulator.
What would be the normal way of displaying the info?
I have a condition set:
If condition one
then display "+0+1"
else
display "+0+2"
*What I am hoping it to display is:
* "this is one"
* "this is two"
* "this is three"
* else display
* "555-555-1234"
* "555-555-0000"
* "555-555-5555"
MY PLIST IS AS FOLLOWS
*
Key Type Value
*Root Dictionary (2 items)
*+0+1 Array (3 items)
*Item 0 String "this is one"
*Item 1 String "this is two"
*Item 2 String "this is three"
*+0+2 Array (3 items)
*Item 0 String "555-555-1234"
*Item 1 String "555-555-0000"
*Item 2 String "555-555-5555"
*
// read property list into memory as an NSData object
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSString *errorDesc = nil;
NSPropertyListFormat format;
// convert static property list into dictionary object
NSDictionary *temp = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
// assign values
self.item1 = [temp objectForKey:#"name1"];
self.item2 = [NSMutableArray arrayWithArray:[temp objectForKey:#"name2"]];
// display values
bigCheese.text = disp1;
numberZero.text = [disp2 objectAtIndex:0];
numberOne.text = [disp2 objectAtIndex:1];
numberTwo.text = [disp2 objectAtIndex:2];
Once you get a dictionary get all values for that dictionary. In your case, get all values from the temp dictionary. It should return you an array containing two arrays.
NSArray *valueArray = [temp allValues];
Now you have two arrays and if you want to access "555-555-5555", do like this:
[[valuesArray objectAtIndex:1] objectAtIndex:2];
Or if you are sure that "name1" and "name2" are the keys of your dictionary you can access it this way:
[[temp objectForKey:#"name1"] objectAtIndex:2];
Once you get this info then it is up to you to decide how you want to display it. I suggest you to DEBUG and see if the dictionary and array objects are created properly first. Not sure what are those disp1 and disp2 in the sample code you posted..
Try using:
NSString *file = [[NSBundle mainBundle] pathForResource:#"Data" ofType:#"plist"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:file];
NSArray *array = [dict objectForKey:#"Array"]; NSLog(#"%#", array);
It's simple, so less chances to do something wrong.

Crashes on this line

NSArray *listItems = [temp componentsSeparatedByString:#","];
Can anyone please tell me why?
temp is an NSString
Here's the entire code
- (NSString *)getStreetAddress
{
NSString* temp = [addressArray objectAtIndex:0];
if (temp != nil) {
NSArray *listItems = [temp componentsSeparatedByString:#","];
temp = [listItems objectAtIndex:0];
}
return temp;
}
EXC_BAD_ACCESS is the error
If execution gets to the line you say, en the most likely problem is that the first item in addressArray has been improperly deallocated while still part of the array. Since the array doesn't check to make sure the object it contains is valid, it will return a pointer to free memory. When you try to access this memory, it crashes. You can try running with NSZombiesEnabled=YES in the environment. If I am correct, you will get a error message logged to the console.

Writing an NSMutableArray to my documents directory fails

I am attempting to cache a web request. Basically I have an app that uses a facebook user's friend list but I don't want to grab it every single time they log in. Maybe refresh once per month. Caching the friend list in a plist in the documents directory seems to make sense for this functionality. I do this as follows:
- (void)writeToDisk {
NSLog(#"writing cache to disk, where cache = %#", cache);
BOOL res = [cache writeToFile:[FriendCache persistentPath] atomically:YES];
NSLog(#"reading cache from disk immediately after writing, res = %d", res);
NSMutableArray *temp = [[NSMutableArray alloc] initWithContentsOfFile:[FriendCache persistentPath]];
NSLog(#"cache read in = %#", temp);
}
+ (NSString *)persistentPath {
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
return [documentsDirectory stringByAppendingPathComponent:#"FriendCache.plist"];
}
These are members of a FriendCache singleton I am using which basically wraps an NSMutableArray. I have verified that the peristentPath method is returning a valid path. As you you can see in the writeToDisk method, I verify there is data in the cache and then I print the result of the write and check if any data could be read back in. There is never data read back in, because the result of the file write is 0.
The output of the cache print is very long, but here is the abbreviated version:
2010-12-28 13:35:23.006 AppName[51607:207] writing cache to disk, where cache = (
{
birthday = "<null>";
name = "Some Name1";
pic = "http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs1324.snc4/7846385648654.jpg";
"pic_big" = "http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs442.snc4/784365789465746.jpg";
"pic_square" = "http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs1324.snc4/7846357896547.jpg";
sex = female;
status = "<null>";
uid = 892374897165;
},
{
birthday = "<null>";
name = "Some Name2";
pic = "http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs625.ash1/54636536547_s.jpg";
"pic_big" = "http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs170.ash2/65465656365666_n.jpg";
"pic_square" = "http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs625.ash1/654635656547_q.jpg";
sex = female;
status = "<null>";
uid = 7658436;
},
...
One thing I checked out is when using writeToFile, I must make sure the object I am writing has valid plist objects. I did check this and here is how I construct the cache object:
- (void)request:(FBRequest*)request didLoad:(id)result{
NSMutableArray *friendsInfo = [[[NSMutableArray alloc] init] autorelease];
for (NSDictionary *info in result) {
NSString *friend_id = [NSString stringWithString:[[info objectForKey:#"uid"] stringValue]];
NSString *friend_name = nil;
NSString *friend_sex = nil;
NSString *friend_relationship_status = nil;
NSString *friend_current_location = nil;
if ([info objectForKey:#"name"] != [NSNull null]) {
friend_name = [NSString stringWithString:[info objectForKey:#"name"]];
}
if ([info objectForKey:#"relationship_status"] != [NSNull null]) {
friend_relationship_status = [NSString stringWithString:[info objectForKey:#"relationship_status"]];
}
if ([info objectForKey:#"sex"] != [NSNull null]) {
friend_sex = [NSString stringWithString:[info objectForKey:#"sex"]];
}
if ([info objectForKey:#"current_location"] != [NSNull null]) {
friend_current_location = [[info objectForKey:#"current_location"] objectForKey:#"name"];
}
NSString *friend_pic_square = [info objectForKey:#"pic_square"];
NSString *friend_status = [info objectForKey:#"status"];
NSString *friend_pic = [info objectForKey:#"pic"];
NSString *friend_pic_big = [info objectForKey:#"pic_big"];
NSString *friend_birthday = [info objectForKey:#"birthday"];
NSDictionary *friend_info = [NSDictionary dictionaryWithObjectsAndKeys:
friend_id,#"uid",
friend_name, #"name",
friend_pic_square, #"pic_square",
friend_status, #"status",
friend_sex, #"sex",
friend_pic, #"pic",
friend_pic_big, #"pic_big",
friend_birthday, #"birthday",
friend_relationship_status, #"relationship_status",
friend_current_location, #"current_location",
nil];
// If the friend qualifies as a single of your gender, add to the friend cache
if ( [AppHelpers friendQualifies:friend_info] == YES) {
[[FriendCache sharedInstance] push:friend_info];
}
}
[[FriendCache sharedInstance] writeToDisk];
}
My push method just wraps the NSMutableArray push:
- (void)push:(id)o {
[cache addObject:o];
}
Can you think of any reason why the write would fail?
Thanks!
So as we already pointed out, it's because of the usage of the NSNull objects.
The best way to avoid this is to create an object Friend, with all of the needed properties. Then you can easily set nil values, something not possible with NSDictionary objects (well, you'd have to remove the key, which is not very good practice).
Then, by implementing the NSCoding protocol, you can easily archive (serialize) your custom object.
This is a much better way of handling your data, and it will become MUCH easier in the future. You'll be able to call messages on the Friend objects, something not possible with NSDictionary.
Use NSError-aware API for NSPropertyListSerialization to get the data and the NSData NSError aware write API so you get a meaningful error helping you understand what your problem might be.

Problem reading a string from an NSDictionary inside an NSMutableArray stored using NSKeyedArchiver

I'm saving some data using a series of NSDictionaries, stored in an NSMutableArray and archived using NSKeyedArchiver.
I'm basically trying to save the states of several instances the class 'Brick', so I've implemented a getBlueprint method like this (slimmed down version)
-(id)getBlueprint
{
// NOTE: brickColor is a string
NSDictionary *blueprint = [NSDictionary dictionaryWithObjectsAndKeys:
brickColor, #"color",
[NSNumber numberWithInt:rotation], #"rotation",
nil];
return blueprint;
}
And so I have another method that creates a new Brick instance when provided with a blueprint.
-(id)initWithBlueprint:(NSDictionary *)blueprint spriteSheet:(NSString *)ssheet
{
if((self == [super init])){
brickColor = [blueprint objectForKey:#"color"];
[self setColorOffset:brickColor];
while(rotation != [[blueprint objectForKey:#"rotation"] intValue]){
[self setRotation:90];
}
}
return self;
}
Which works when I pass it a 'fresh' blueprint, but not when I read a blueprint from a saved file... sort of. For example, the rotation will work, but changing the color wont. So while I can read the value of brickColor using
NSLog(#"brick color %#", [blueprint objectForKey:#"color"]);
if I try something like
if(brickColor == #"purple"){
colorOffset = CGPointMake(72,36);
NSLog(#"Changed offset for -- %# -- to %#", color, NSStringFromCGPoint(colorOffset));
}
And I know that color is purple, the condition doesn't return true. I thought it might be that somehow NSKeyedUnarchiver changed a string into something else, but the following test returns true.
if([color isKindOfClass:[NSString class]]){
NSLog(#"%# IS A STRING", color);
}else{
NSLog(#"!!!!! COLOR IS A NOT STRING !!!!!");
}
As I said, this isn't a problem if I try to use a freshly created NSDictionary as a blueprint, only when a blueprint is archived and then read back in.
So, as usual, I'm wondering if anyone has any ideas why this might be happening.
incase it's relevant, here's how the data is being stored and recieved.
// Saving
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-(void)buildLevelData{
levelData = [[NSMutableArray alloc] initWithCapacity:100];
for(brickSprite *brick in spriteHolder.children){
[levelData addObject:[brick getBlueprint]];
}
}
-(void)saveLevel
{
[self buildLevelData];
NSData *rawDat = [NSKeyedArchiver archivedDataWithRootObject:levelData];
if([self writeApplicationData:rawDat toFile:saveFileName]){
NSLog(#"Data Saved");
}else{
NSLog(#"ERROR SAVING LEVEL DATA!");
}
[[Director sharedDirector] replaceScene:[MainMenu scene]];
}
- (BOOL)writeApplicationData:(NSData *)data toFile:(NSString *)fileName {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
if (!documentsDirectory) {
NSLog(#"Documents directory not found!");
return NO;
}
NSString *appFile = [saveDir stringByAppendingPathComponent:fileName];
return ([data writeToFile:appFile atomically:YES]);
}
// Loading
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
- (void) loadRandomMapFrom:(NSString *)dir
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsDir = [paths objectAtIndex:0];
if(!docsDir){
NSLog(#"Cound Not Find Documents Directory When trying To Load Random Map");
return;
}
dir = [docsDir stringByAppendingPathComponent:[NSString stringWithFormat:#"/%#", dir]];
// we'll also set the file name here.
NSArray *existingFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:dir error:nil];
// get last file for this test
NSString *filePath = [dir stringByAppendingPathComponent:[existingFiles objectAtIndex:([existingFiles count] - 1)]];
NSMutableArray *levelData = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
[self buildMapWithData:levelData];
}
-(void)buildMapWithData:(NSMutableArray *)lData
{
for(NSDictionary *blueprint in lData){
brickSprite *brick = [[brickSprite alloc] initWithBlueprint:blueprint spriteSheet:#"blocks.png"];
[spriteHolder addChild:brick];
}
}
Sorry about the mess of a question. There's a lot going on that I'm struggling to fully understand myself so it's hard to break it down to the bare minimum.
You should always compare strings with [firstString isEqualToString:secondString], because firstString == secondString only checks for pointer equality, e.g. if both strings are stored at the same location (which they'll never be when comparing dynamically created objects and string constants).