Writing an NSMutableArray to my documents directory fails - iphone

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.

Related

google places api - json return correct data and then an exception occurs

I'm using google places API,
The json return valid results, In my async methods I'm setting markers per each result, the method ends and all of a sudden i get the following thread 1: EXC_BAD_ACCESS code:2 address:0x9243e8, in the output window i get the following: [71539:c07] -[__NSCFString CGImage]: unrecognized selector sent to instance 0xb19dd10
I have no clue how to debug this, or where to look for the problem, all my parameters look good.
thanks
I suspect what you are doing is as a commenter says, treating the returned JSON object as a type it is not. Specifically, from your error, you are treating a string like an image. Generally, JSON doesn't not contain actually images, so when you deserialize it, you will not get images. In the Google Places API, it does not send you images. Instead, it send you URLs to images. You will have to download the images using the URLs you get from the Places API.
Here is some code I use. This method gets the place list from the API based on a CLLocation and returns it as a much more easily digested NSArray of NSDictionary objects each containing the important met-data pertaining to a place:
-(NSArray*)getPlaceListForLocation:(CLLocation*)inLocation {
NSString* requestStr = [NSString stringWithFormat:#"https://maps.googleapis.com/maps/api/place/nearbysearch/json?key=%#&location=%f,%f&radius=100&sensor=true",self.googleAPIKey,inLocation.coordinate.latitude,inLocation.coordinate.longitude];
NSURL* requestURL = [NSURL URLWithString:requestStr];
NSLog(#"Fetching place data with URL = %#", requestURL);
NSData* requestData = [NSData dataWithContentsOfURL:requestURL];
if ( nil != requestData ) {
NSError* jsonError = nil;
id resultsObj = [NSJSONSerialization JSONObjectWithData:requestData options:0 error:&jsonError];
if ( nil == resultsObj ) {
if ( nil != jsonError ) {
NSLog(#"%#",jsonError.description);
}
return nil;
}
NSArray* itemList = [resultsObj objectForKey:#"results"];
if ( nil == itemList || itemList.count == 0) {
return nil;
}
NSMutableArray* placeList = [NSMutableArray arrayWithCapacity:itemList.count];
for ( NSDictionary* item in itemList ) {
NSString* name = [item objectForKey:#"name"];
NSString* iconURL = [item objectForKey:#"icon"];
NSString* vicinity = [item objectForKey:#"vicinity"];
NSDictionary* geometryDict = [item objectForKey:#"geometry"];
NSDictionary* locationDict = [geometryDict objectForKey:#"location"];
NSMutableDictionary* itemDict = [NSMutableDictionary dictionaryWithCapacity:2];
if ( nil != name ) {
[itemDict setObject:name forKey:#"name"];
}
if (nil != iconURL ) {
[itemDict setObject:iconURL forKey:#"iconurl"];
}
if ( nil != vicinity ) {
[itemDict setObject:vicinity forKey:#"vicinity"];
}
if ( nil != locationDict ) {
NSString* latitudeStr = [locationDict objectForKey:#"lat"];
NSString* longitudeStr = [locationDict objectForKey:#"lng"];
double latitude = [latitudeStr doubleValue];
double longitude = [longitudeStr doubleValue];
CLLocation* placeLoc = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude];
CLLocationDistance distance = [inLocation distanceFromLocation:placeLoc];
[itemDict setObject:[NSNumber numberWithDouble:distance] forKey:#"distance"];
}
[placeList addObject:itemDict];
}
return placeList;
}
return nil;
}
Then I display these in a table view using the returned NSArray as the data source. In order to display the icon, I use something like MKNetworkImageView in order to fetch the icon based on the URL.

Adding values into my plist

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

Reduce time to perform big operation on iphone app

When I launch an app, I need to retrieve all contacts from Address Book & store it in array to display it in table view. I wrote this code in viewDidAppear method. While contacts are retrieving from Address Book , I am showing activity indicator. I have around 1100 contacts in my address book. For this it took 14 seconds to retrieve data & store it in array. Its not acceptable time. So I need to optimize this & reduce time to max 2 to 3 seconds.
I need all contacts because when my app launches , I need to search for contacts so I need all the data available in my array.
How can I reduce this timing ? If you need more information just let me know.
Any kind of help is highly appreciated. Thanks.
UPDATE 1 : My code
- (NSMutableArray*)getAddressBookData {
self.tempArray = [[NSMutableArray alloc]init];
addressBook = ABAddressBookCreate();
APP_DELGATE.people = (NSArray*)ABAddressBookCopyArrayOfAllPeople(addressBook);
peopleCount = [APP_DELGATE.people count];
for (int i=0; i<peopleCount; i++) {
ABRecordRef record = [APP_DELGATE.people objectAtIndex:i];
NSNumber *recordId = [NSNumber numberWithInteger:ABRecordGetRecordID(record)];
NSLog(#"record id is %#",recordId);
// Get fname, lname, company
NSString *fnm = (NSString *)ABRecordCopyValue(record, kABPersonFirstNameProperty) ;
NSString *lnm = (NSString *)ABRecordCopyValue(record, kABPersonLastNameProperty) ;
NSString *comp = (NSString*)ABRecordCopyValue(record,kABPersonOrganizationProperty);
// Get Ph no
ABMultiValueRef phoneNumberProperty = ABRecordCopyValue(record, kABPersonPhoneProperty);
NSArray* phoneNumbers = [self getPhoneNoWithoutSymbols:(NSArray*)ABMultiValueCopyArrayOfAllValues(phoneNumberProperty)];
NSString *strPhoneNos = [self getStringRepresentaionFromArray:phoneNumbers];
NSLog(#"strPhoneNos => %#",strPhoneNos);
// Get emails
ABMultiValueRef emailProperty = ABRecordCopyValue(record, kABPersonEmailProperty);
NSArray* emails = (NSArray*)ABMultiValueCopyArrayOfAllValues(emailProperty);
NSString *strEmails = [self getStringRepresentaionFromArray:emails];
NSLog(#"strEmails => %#",strEmails);
// Get URL
ABMultiValueRef urlProperty = ABRecordCopyValue(record, kABPersonURLProperty);
NSArray* urls = (NSArray*)ABMultiValueCopyArrayOfAllValues(urlProperty);
NSString *strURLs = [self getStringRepresentaionFromArray:urls];
NSLog(#"strURLs => %#",strURLs);
// Get Address
ABMultiValueRef address=ABRecordCopyValue(record, kABPersonAddressProperty);
CFDictionaryRef dic=nil;
NSMutableArray *addressArray = [[NSMutableArray alloc]init];
for (int index=0; index<ABMultiValueGetCount(address); index++) {
dic=ABMultiValueCopyValueAtIndex(address, index);
NSString* labelName=(NSString*)ABMultiValueCopyLabelAtIndex(address, index);
if (labelName) {
NSString *street =(NSString*) CFDictionaryGetValue(dic, kABPersonAddressStreetKey);
NSString *city= (NSString*)CFDictionaryGetValue(dic, kABPersonAddressCityKey) ;
NSString *state= CFDictionaryGetValue(dic, kABPersonAddressStateKey);
NSString *country=CFDictionaryGetValue(dic, kABPersonAddressCountryKey);
NSString *zipcode=CFDictionaryGetValue(dic, kABPersonAddressZIPKey);
NSString *addressDetails=#"";
if (street) {
addressDetails=[NSString stringWithFormat:#"%# ",street];
}
if (city) {
addressDetails=[NSString stringWithFormat:#"%# %# ",addressDetails,city];
}
if (state) {
addressDetails=[NSString stringWithFormat:#"%# %# ",addressDetails,state];
}
if (country) {
addressDetails=[NSString stringWithFormat:#"%# %# ",addressDetails,country];
}
if (zipcode) {
addressDetails=[NSString stringWithFormat:#"%# %# ",addressDetails,zipcode];
}
[addressArray addObject:addressDetails];
}
}
NSString *strAddress = [self getStringRepresentaionFromArray:addressArray];
NSLog(#"strAddress => %#",strAddress);
// Get Notes
NSString *noteString=(NSString *)ABRecordCopyValue(record, kABPersonNoteProperty);
// Get Birthdate
NSDate *birthDate=(NSDate*)ABRecordCopyValue(record, kABPersonBirthdayProperty) ;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MMMM dd yyyy"];
NSString *birthdateString = [formatter stringFromDate:birthDate];
[formatter release];
// Get user image
UIImage *image = nil;
if( ABPersonHasImageData( record ) ) {
NSData *imageData = (NSData*)ABPersonCopyImageData(record);
image = [UIImage imageWithData:imageData];
[imageData release];
}
// Create User object & add it to array
User *user = [[User alloc]initUserWithUniqID:recordId.intValue FirstName:fnm lastName:lnm company:comp phoneNumbers:strPhoneNos emails:strEmails urls:strURLs address:strAddress notes:noteString dob:birthdateString userImage:image];
[self.tempArray addObject:user];
[user release];
}
self.tempArray = [NSMutableArray arrayWithArray:[self.tempArray sortedArrayUsingSelector:#selector(compare:)]];
APP_DELGATE.allUsersArray = self.tempArray;
return tempArray;
}
-(NSMutableArray*)getPhoneNoWithoutSymbols:(NSArray*)array {
self.phNoArray = [[NSMutableArray alloc]init];
for (NSString *str in array) {
[self.phNoArray addObject:[self getPhNo:str]];
}
return self.phNoArray;
}
-(NSString*)getPhNo:(NSString*)str {
NSString *str0 = [str stringByReplacingOccurrencesOfString:#" " withString:#""];
NSString *str1 = [str0 stringByReplacingOccurrencesOfString:#"(" withString:#""];
NSString *str2 = [str1 stringByReplacingOccurrencesOfString:#")" withString:#""];
NSString *str3 = [str2 stringByReplacingOccurrencesOfString:#"-" withString:#""];
return str3;
}
-(NSString*)getStringRepresentaionFromArray:(NSArray*)array {
return [array componentsJoinedByString:DELIMITER_SYMBOL];
}
Firstly, try some general approaches to reduce time by using more optimized code and less repetition of code and also through less use of loops or may be only iterate loops only till the data is obtained. Also you can check whether your code is properly distributed as far as the Time Profiling is concerned.
Secondly, we feel that time required is more because user is shown an Activity Indicator till 14 seconds. If you don't show it and don't block the User Interface till data is getting copied into your array, then user may feel that it is more smooth So here is how you can do that:
You can use NSThread to allow the application to launch while you retrieve all your data from the AddressBook and Store it in your array.
You can use
[NSThread detachNewThreadSelector:#selector(fetchAddressContacts:) withObject:nil];
For more information you can refer to detachNewThreadSelector: from NSThread
So basically in AppDelegate you need to code as following in AppDelegate.m
-(void)applicationDidFinishLaunching:(UIApplication *)application {
[NSThread detachNewThreadSelector:#selector(fetchAddressContacts) withObject:nil];
}
-(void)fetchAddressContacts {
//Do your stuff to copy your contacts from address book to array
// Once you finish this task you can trigger an NSNotification which could be caught on some other viewController or implement a delegate to transfer data to a viewController and proper actions can be executed once the data is loaded.
}
Let me know if you need more help.
Hope this helps you.
Perform your contacts retrieval method diagnostics and identify what calls are causing the bottleneck here.
Share your code and describe the bottlenecks
Any operation taking serious amount of time should be performed in a thread. In this case, I'd gather the data on first startup only and store them internally. You can refresh the primary data anytime later on demand (refresh button?) otherwise work with the internally stored "clone" of the contacts rather than calling any time-consuming operation on every app's start.
BTW: Be careful about the address book contacts these days :-)
When your app first install fetch all the contacts and save in coredate/plist file when next time app opens then show the contacts from the storage which is very fast as compare to fetching contacts from iPhone database. Now the problem how to update contacts which newly added, so for every contact there is property called "kABPersonModificationDateProperty" and "kABPersonCreationDateProperty" ,use this to find only updated contacts and add in the storage. Hope this helps.

How to display Xpath on the iPhone

I'm trying to extract the weather information from here using Xpath on the iPhone. As of now it parses all the data but I'm stuck on how to extract the content and display it in a table.
This is what I have so far:
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:[ #"http://aviationweather.gov/adds/metars/?station_ids=1234&std_trans=translated&chk_metars=on&hoursStr=most+recent+only&submitmet=Submit"stringByReplacingOccurrencesOfString:#"1234" withString:self.title]]];
TFHpple * doc = [[TFHpple alloc] initWithHTMLData:data];
NSArray * elements = [doc searchWithXPathQuery:#"//table[1]//tr"];
NSLog(#"%#", elements);
TFHppleElement * element = [elements objectAtIndex:0];
[element content]; // Tag's innerHTML
[element tagName]; // "a"
[element attributes]; // NSDictionary of href, class, id, etc.
[element objectForKey:#"href"]; // Easy access to single attribute
If anybody needs to see what its outputting so far, let me know.
Thanks,
Andrew
I had the same issue I got to the point your at and didn't no where to go but I end up implementing this code. Hope it helps there is still little bits need to make it work correctly but do to the nature of the app I have developed this is all I can give you. its not much more its just the actual implementation into your code that you need really.
#import "XPathQuery.h"
NSMutableArray *weatherArray = [[NSMutableArray arrayWithArray:0]retain]; // Initilize the NSMutableArray can also be done with just an NSArray but you will have to change the populateArray method.
NSString *xPathLookupQuery = #"//table[1]//tr"; // Path in xml
nodes = PerformXMLXPathQuery(data, xPathLookupQuery); // Pass the data in that you need to search through
[self populateArray:weatherArray fromNodes:nodes]; // To populate multiple values into array.
session = [[self fetchContent:nodes] retain]; // To populate a single value and returns value.
- (void)populateArray:(NSMutableArray *)array fromNodes:(NSArray *)nodes
{
for (NSDictionary *node in nodes) {
for (id key in node) {
if ([key isEqualToString:#"nodeContent"]) {
[array addObject:[node objectForKey:key]];
}
}
}
}
You only need either the above code or below code unless you want both.
- (NSString *)fetchContent:(NSArray *)nodes
{
NSString *result = #"";
for (NSDictionary *node in nodes) {
for (id key in node) {
if([key isEqualToString:#"nodeContent"]) {
result = [node objectForKey:key];
}
}
}
return result;
}

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).