iPhone - Registering an Array containing NSNulls with NSUserDefaults - iphone

I have the Array defined below.
NSMutableArray *tempMPArray = [NSMutableArray arrayWithCapacity:16];
for (int i=0; i < chapters; i++)
{
[tempMPArray addObject:[NSNull null]];
}
Every time I use it as a one of the objects of a dictionary below to register default values it crashes with EXC_BAD_ACCESS.
[[NSUserDefaults standardUserDefaults] registerDefaults:myDict];
If I replace the objects in the array with any other object NSNumber etc it works fine. What am I doing wrong with my array that NSUserDefaults rejects it ? The stack trace or NSZombie does not give any additional info.

While the solution may be a "work-around" I don't think it answers the underlying question. By providing a dictionary with [NSNull null] values he is providing NSUserDefaults with a valid dictionary. Why is it crashing? Is it something he is doing "wrong" or is it a bug in registerDefaults?
There may be circumstances where it is important to know whether say a user has entered a value (ie there is a valid string) or not entered a value (null) and the method of creating a "valid but empty string" can't determine if the empty string was because the user created a string with no characters or if he had never created a string at all. You may want different logic in these two cases.
I'm in the same boat, I have an NSUserDefault value I want to be [NSNull null] and everything I've read suggests the "right" way to put a null value in a dictionary is with [NSNull null] and my dictionary creates fine. But registerDefaults with this valid dictionary causes a crash. That suggests a bug in the implementation of registerDefaults doesn't it? What is wrong with the following and why does it crash?
NSArray *defaultValues = [NSArray arrayWithObjects:[NSNull null], nil];
NSArray *CurrentKeys = [NSArray arrayWithObjects: #"NullKey", nil];
NSDictionary *resourceDict = [NSDictionary dictionaryWithObjects:defaultValues forKeys:CurrentKeys];
[[NSUserDefaults standardUserDefaults] registerDefaults:resourceDict];
Ultimately, i think the answer is that the userDefaults get written out as a plist and plists don't support NSNull. if that's the case, the documentation for registerDefaults should say that only plist object types are allowed in the dictionary

Here's a better solution!
Define a static after #implementation
NSString *const Null = #"";
Then
NSMutableArray *tempDSArray = [NSMutableArray arrayWithCapacity:16];
for (i=0; i < chapters; i++)
{
[tempDSArray addObject:Null];
}
Everything else should work fine!

Related

Port array of strings to array of dictionaries each containing a string

I'm trying to port the strings in an array into an array of dictionaries where each dictionary contains one of the strings and a boolean. Hopefully this makes sense, but I'll try and make a diagram:
At the moment I have:
<array>
-<string1>
-<string2>
...
</array>
But I want:
<array>
-<dictionary1>
--<string1>
--<bool>
-</dictionary1>
-<dictionary2>
--<string2>
--<bool>
-</dictionary2>
...
</array>
I've tried creating a loop to cycle through the array of strings, but it doesn't seem to work.
The ultimate goal is to save this new array of dictionaries to the NSUserDefaults (which I'm also fairly unfamiliar with). This is what I have so far, and any help would be much appreciated!
// Get the current array from the user defaults.
NSArray *tempArray = [[NSUserDefaults standardUserDefaults] objectForKey:#"myArrayKey"];
self.myArray = [tempArray mutableCopy];
// Loop to cycle through the array of strings
for(int i = 0; i < [aList count]; i++)
{
// Boolean to go in the dictionary with the string.
checked = NO;
// Create a dictionary, and set it with two objects and two keys.
//First object is the string in the array we are cycling through, the second object is the boolean.
NSDictionary *tempDict = [[NSDictionary alloc] initWithObjectsAndKeys:[aList objectAtIndex:i], #"Title", checked, #"checked", nil];
// Add this dictionary to the new array of dictionaries.
[self.myArray addObject:tempDict];
// This NSLog gives me 0 - but gives me 0 seven times (the number of items in the array I am cycling through) so it is definitely cycling through the array.
NSLog(#"My Array Count: %i", [myArray count]);
// Release the tempDict.
[tempDict release];
}
// Write this new array of dictionaries back to NSUseDefaults.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:myArray forKey:#"myArrayKey"];
[defaults synchronize];
As Daniel pointed out (and I shortly realised through some tests), the fact that the array from NSUserDefaults was nil stopped it from working.
How about inserting a line
[tempDict retain];
next
[self.myArray addObject:tempDict];

How to create a NSArray of unknown NSStrings?

For example, I have #"John", #"Peter", ..., #"May" and need to construct NSArray:
[#"John", #"Peter", ..., #"May"]
The number of NSString is unknown and is taking from an import text file. As NSArray does not support appending new element, how can I create NSArray?
Thanks
UPDATE, let me rephrase the question. How can I create the dynamic array paremeter required by the follow function call?
[segmentedAttributes attributesWithTitlesArray:[NSArray arrayWithObjects:#"John", #"Peter", #"May", nil]]
You don't.
You misunderstand the library behavior.
It is true that there is a convenience constructor arrayWithObjects: which is used thus:
NSArray* array=[NSArray arrayWithObjects:#"Low", #"Medium", #"High", nil];
But this does not create an array with nil at the end. This nil is just to signify the end of the variable-length argument list. It just creates an NSArray with three elements, not four with the last one nil.
You just need to create an NSArray containing the required elements, and pass it to the library function. For example:
NSMutableArray*array=[NSMutableArray array];
while(...){
... get a string ...
[array addObject: string];
}
SCSegmentedAttributes*attributes=[SCSegmentedAttributes attributesWithSegmentTitlesArray:array];
should work, without adding a nil or [NSNull null].
You can't store nil in a Foundation collection class. Instead, you can use [NSNull null]. Use an NSMutableArray, then when you want to add your 'nil' object, use [NSNull null].
Note that when you want to see if an object is [NSNull null] later on, that method will return the same instance every time, so you can do a direct point equality test, like this:
for (id anObject in myArray) {
if (anObject == [NSNull null]) {
NSLog(#"object is nil");
}
else {
NSLog(#"object is not nil: %#", anObject);
}
}
create mutable array then just use
NSString *myString = [NSString stringWithFormat:#"ur new string"];
[myArray addObject:myString];
you have to add object type to array when adding new abject in mutable array.
hope this will help

Can't put a NSString into a NSMutableArray and store it in NSUserDefaults

I am receiving this error: '-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object'
In the ViewDidLoad I initialized my array:
id object = [[NSUserDefaults standardUserDefaults] objectForKey:#"array"];
if (object)
{
locationArray = object;
NSLog(#"retrieved", object);
}
else
{
locationArray = [[NSMutableArray alloc] init];
NSLog(#"init");
}
Then, I am trying to add the data to locationArray:
ABMultiValueRef multi = ABRecordCopyValue(person, property);
NSUserDefaults *locatie = [NSUserDefaults standardUserDefaults];
// Set up an NSArray and copy the values in.
NSArray *theArray = [(id)ABMultiValueCopyArrayOfAllValues(multi) autorelease];
//everything goes fine the first time, but the second time i receive an error after at this code:
[locationArray addObject:theArray];
[locatie setObject:locationArray forKey:#"array"];
Every first time I select an address everything is fine. But every second time I am receiving that error.
What did I do wrong?
NSString *fname = (NSString *)ABRecordCopyValue(person, kABPersonFirstNameProperty);
NSMutableString *lname = (NSMutableString *)ABRecordCopyValue(person, kABPersonLastNameProperty);
What makes you think ABRecordCopyValue is going to return a mutable string here?
Just telling the compiler that it will return a mutable string (which is all “(NSMutableString *)” does) doesn't mean it will. If the Address Book documentation doesn't specify that this will return a mutable string, assume it won't and create a mutable string yourself.
Speaking of which…
NSMutableString *name = [[NSMutableString alloc] init];
Here's the string you should be appending to. You don't need lname to be mutable, because this string is mutable.
NSMutableString *space = #" ";
fname = [fname stringByAppendingFormat:space];
fname = [fname stringByAppendingFormat:lname];
By doing this, you waste the mutable string you created. You are creating two intermediate immutable strings here, not appending to the mutable string.
name = fname;
And here, you throw away the mutable string entirely (and thereby leak it since you never released it), replacing it with the immutable string you got from your series of stringByAppendingFormat: messages.
What you should do is send the name mutable string an appendFormat: message, passing both the first and last name strings. You don't need the space string; you can include that in the format string.
See also the NSMutableString docs.
[nameArray addObject:fname];
At no point prior to this statement have you created an NSMutableArray object and stored its pointer in the nameArray variable. Not in any code you've shown, anyway.
Until you do that, this variable holds nil, the pointer to no object. The addObject: message does nothing because that's what messages to nil do: Nothing. Logging the array you don't have produces “(null)” because that's the description of nil.
if (nameArray == nil) {
NSLog(#"NO DATA TO RETRIEVE FROM USERDEFAULTS");
You aren't showing any code that retrieves from user defaults. Even if you did, it would return an immutable array as Sven said; you would have to make a mutable copy.
This is the best I can do without a description of the problem. We may be able to provide more and better suggestions if you edit your question to tell us what happens when you run the above code, and not just what doesn't happen.
The problem occures when you get the locationArray from the NSUserDefaults and try to insert some object ([locationArray addObject:theArray];). The object you get from the NSUserDefaults is not mutable, so you have to create a mutable copy:
NSMutableArray* locationArray = [[NSUserDefaults standardUserDefaults] objectForKey:#"array"];
if (locationArray != nil)
locationArray = [[locationArray mutableCopy] autorelease];
else
locationArray = [NSMutableArray array];
And don't forget to check if fname is not nil before adding it to array

How can i get Original order of NSDictionary/NSMutableDictionary?

i have created NSMutableDictionary with 10 keys.Now i want to access NSMutableDictionary keys in a same order as it was added to NSMutableDictionary (using SetValue:* forKey:* );
How can i achieve that ?
If you absolutely must use a dictionary container, you have to use a key that is sortable by the order in which you add key-value pairs. Thus, when creating your dictionary, you use a key that is an auto-incrementing integer or similar. You can then sort on the (integer) keys and retrieve the values associated with those keys.
If you do all of that, however, you may as well just use an NSMutableArray and add values to the array directly! It will be much faster and require less code. You just retrieve objects in order:
for (id obj in myArray) { /* do stuff with obj... */ }
NSMutableDictionary can't do that. Take a look at e.g. Matt Gallaghers OrderedDictionary.
I wrote a quick method to take a source array (of objects that are all out of order) and a reference array (that has objects in a desired (and totally arbitrary) order), and returns an array where the items of the source array have been reorganized to match the reference array.
- (NSArray *) reorderArray:(NSArray *)sourceArray toArray:(NSArray *)referenceArray
{
NSMutableArray *returnArray = [[NSMutableArray alloc] init];
for (int i = 0; i < [referenceArray count]; i++)
{
if ([sourceArray containsObject:[referenceArray objectAtIndex:i]])
{
[returnArray addObject:[arrReference objectAtIndex:i]];
}
}
return [returnArray copy];
}
Note that this is very fragile. It uses NSArray's containsObject: method, which ultimately will call NSObject's isEqual:. Basically, it should work great for arrays of NSStrings, NSNumbers, and maybe NSDates (haven't tried that one yet), but outside of that, YMMV. I imagine if you tried to pass arrays of UITableViewCells or some other really complex object, it would totally sh*t itself, and either crash or return total garbage. Likewise if you were to do something like pass an array of NSDates as the reference array and an array of NSStrings as the source array. Also, if the source array contains items not covered in the reference array, they'll just get discarded. One could address some of these issues by adding a little extra code.
All that said, if you're trying to do something simple, it should work nicely. In your case, you could build up the reference array as you are looping through your setValue:forKey:.
NSMutableArray *referenceArray = [[NSMutableArray alloc] init];
NSMutableDictionary *yourDictionary = [[ NSMutableDictionary alloc] init];
for (//whatever you are looping through here)
{
[yourDictionary setValue://whatever forKey:key];
[referenceArray addObject:key];
}
Then, when you want to loop over your items in the order they came in, you just
for (NSString *key in [self reorderArray:[myDict allKeys] toArray:referenceArray])
Actually you have a reference array in order manner then why you have to add to one more array.So i guess this approach is not good.Please consider my opinion.
Although #GenralMike 's answer works a breeze, it could be optimized by leaving off the unnecessary code as follows:
1) Keep an array to hold reference to the dictionary keys in the order they are added.
NSMutableArray *referenceArray = [[NSMutableArray alloc] init];
NSMutableDictionary *yourDictionary = [[ NSMutableDictionary alloc] init];
for (id object in someArray) {
[yourDictionary setObject:object forKey:someKey];
[referenceArray addObject:someKey]; // add key to reference array
}
2) Now the "referenceArray" holds all of the keys in order, So you can retrieve objects from your dictionary in the same order as they were originally added to the dictionary.
for (NSString *key in referenceArray){
//get object from dictionary in order
id object = [yourDictionary objectForKey:key];
}

NSUserDefaults won't save NSDictionary

I'm writing an application which uses NSUserDefaults as the data storage mechanism, and am hitting a problem when trying to save data (that conforms to the Property List protocols):
+ (BOOL)storeAlbum:(Album *)album
{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *albums = (NSMutableDictionary *)[prefs objectForKey:#"my_adventure_book_albums"];
NSLog(#"Existing albums: %#",albums);
if (!albums)
albums = [NSMutableDictionary dictionaryWithObject:album forKey:#"album"];
else
[albums setObject:album forKey:#"album"];
NSLog(#"%#",album);
[prefs setObject:albums forKey:#"my_adventure_book_albums"];
return [prefs synchronize];
}
I get this output:
2010-06-29 17:17:09.929 MyAdventureBook[39892:207] Existing albums: (null)
2010-06-29 17:17:09.930 MyAdventureBook[39892:207] test
2010-06-29 17:17:09.931 MyAdventureBook[39892:207] *** -[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '{
album = test;
}' of class 'NSCFDictionary'.
The description method of Album looks like:
- (NSString *)description
{
// Convert to a NSDictionary for serializing
if (!title) title = #"";
if (!date) date = [NSDate dateWithTimeIntervalSinceNow:0];
if (!coverImage) coverImage = #"";
if (!images) images = [[NSArray alloc] initWithObjects:#"",nil];
//NSDictionary *dict = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:title,date,coverImage,images,nil] forKeys:[NSArray arrayWithObjects:#"title",#"date",#"coverImage",#"images",nil]];
//NSDictionary *dict = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:title,nil] forKeys:[NSArray arrayWithObjects:#"title",nil]];
//return [dict description];
return #"test";
}
All of the commented-out lines have the same result, so I just decided to see if the NSString "test" would work, which it (of course) doesn't.
But the object you put inside the dictionary, an Album* is most likely not a property list object, is it? Every object, all the way down, needs to be a property list object for this to work. A description method isn't good enough to make this happen.
As a workaround, you can use NSCoding and an NSKeyedArchiver to write out your dictionary to an NSData, which you can store among the preferences.
You can only put basic foundation types into a property list. NSUserDefaults writes preferences out as a property list. See here for property list allowed types. In a nutshell, it is numbers, strings, data, dates, and arrays and dictionaries of those. Dictionaries must have string keys.
NSUserDefaults always returns immutable objects, so you can't just cast them to mutable. Do [prefs objectForKey:#"my_adventure_book_albums"] mutableCopy] (and remember to release it when finished).