iphone - mutableArray cannot store nil objects - iphone

I have a mutable array that is retained and storing several objects. At some point, one object may become nil. When this happens the app will crash, because arrays cannot have nil objects. Imagine something like
[object1, object2, object3, nil];
then, object2 = nil
[object1, nil, object3, nil];
that is not possible because nil is the end of array marker. So, how can I solve that? thanks for any help.

Use [NSNull null] if you have to store an empty placeholder object.
For example:
NSArray * myArray = [NSArray arrayWithObjects:obj1, [NSNull null], obj3, nil];
myArray will contain 3 objects. When you retrieve the object, you can do a simple pointer equality test to see if it's the Null singleton:
id object = [myArray objectAtIndex:anIndex];
if (object == [NSNull null]) {
//it's the null object
} else {
//it's a normal object
}
EDIT (responding to a comment)
#Mike I think you're getting confused with what's actually going on.
If you have:
id obj = ...;
Then obj contains an address. It does not contain an object. As such, if you do NSLog(#"%p", obj), it'll print something like 0x1234567890. When you put obj into the array, it's not copying the object, it's copying the address of the object. So the array actually contains 0x1234567890. Therefore, when you later do: obj = nil;, you're only affecting the pointer outside of the array. The array will still contain 0x1234567890.

Related

Copy an Array - Bad Design?

I learn objective-C from Stanford iTunes and i wonder how i should copy a NSMutableArray to NSArray without initialization. I mean:
Is this is correct? with "lazy initialization".
-(void)copyAnArray:(NSMutableArray*)listOfElements {
if(privateElementsLists == nil)
privateElementsLists = [[NSArray alloc] initWithArray:listOfElements copyItems:YES];
else
privateElementsLists = listOfElements;
}
is this a bad design?
I want to addobjects to mutable array in one class, and then when i'm finish copy entire NSMutableArray to NSArray.
And another question: Why i have to use copyItems:YES when I use initWithArray? And what's deep copy?
You can copy a mutable array to a new array with initWithArray: or this way:
privateElementsLists = [NSArray arrayWithArray:listOfElements];
then you are creating a new array where each of its elements is the same object that figures in the original array. If you write:
privateElementsLists = [NSArray arrayWithArray:listOfElements copyItems:YES];
then the new array have, for each element, a copy of the element in original array. They are not the same object but a copy. Of course, that objects have to be able to respond to copy.
You can even do this:
privateElementsLists = (NSArray*) listOfElements ;
Then the array is exactly the same as the original one. No new array here. But as you have casted it with NSArray pointer class, you can use it as if it is a NSArray instead of a NSMutableArray. As you know, every NSMutableArray is a NSArray (inherited class).
As Joseph DeCarlo stated, you don't need to copy NSMutableArray to NSArray if the only thing you do is to create the array in one place to use it somewhere else. For example this statement is valid:
NSArray* newArray = [NSMutableArray array];
Or in the code:
-(NSArray*)returnAnArray
{
NSMutableArray* editableArray = [NSMutableArray array];
[editableArray addObject:[[NSObject alloc] init]]; //an exemplary object added to the array
return editableArray;
}
That said, however, in some specific cases casting NSMutableArray to NSArray may not be safe, e.g. if the original array was stored in an instance variable. Adding or removing objects to/from that array may cause a crash if the returned array is enumerated at the same time. For example:
-(void)createArray
{
self->editableArray = [NSMutableArray array]; // instance variable: NSMutableArray* editableArray
}
-(void)addObjectToArray
{
[self->editableArray addObject:[[NSObject alloc] init]];
}
-(NSArray*)getArray
{
return self->editableArray;
}
-(void)enumerateArray
{
for(NSObject obj in [self getArray])
{
// do something with obj
}
}
If addObjectToArray is called at the same time as enumerateArray (e.g. from a background thread) the application will crash because the underlying array is changing while it is being enumerated. It doesn't matter that it was returned as NSArray*. In a case like this you would need to either add #synchronized to synchronize access to the same object by multiple threads, or copy the entire array with arrayWithArray: as suggested. Note, however, that the documentation doesn't say if arrayWithArray: is thread safe so I would add #synchronized around the call to arrayWithArray: anyway.

Question about deep copying example

In the Beginning iPhone 4 book, the author has this code to create a category for creating a deep copy of an NSDictionary that has an NSArray of names for each letter of the alphabet to show an example of an indexed table with a search bar.
#import "NSDictionary-MutableDeepCopy.h"
#implementation NSDictionary (MutableDeepCopy)
- (NSMutableDictionary *) mutableDeepCopy {
NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
NSArray *keys = [self allKeys];
for (id key in keys) {
id oneValue = [self valueForKey:key];
id oneCopy = nil;
if ([oneValue respondsToSelector:#selector(mutableDeepCopy)]) oneCopy = [oneValue mutableDeepCopy];
else if ([oneValue respondsToSelector:#selector(mutableCopy)]) oneCopy = [oneValue mutableCopy];
if (oneCopy == nil)
oneCopy = [oneValue copy];
[returnDict setValue:oneCopy forKey:key];
[oneCopy release];
}
return returnDict;
}
#end
Can someone explain the for loop logic? I'm not sure what he's trying to do in seeing which value responds to which selector, and why it would be added to the dictionary. Thanks.
So, the for loop simply iterates through all the keys in the dictionary. Beforehand, we create a new dictionary called returnDict - this will be what we return.
For each key in the dictionary we want to copy, we...
Get the object stored for that key ([self valueForKey:key]), and save it into a variable called oneValue.
If oneValue implements our mutableDeepCopy method (ie, it's an NSDictionary) go call it, and assign the return value into a variable called oneCopy.
Else, we see if oneCopy implements the mutableCopy method. If it does, we put the output into the oneCopy variable.
At this point, we check to see if following steps (2) and (3) the oneCopy variable has had anything assigned to it (if (oneCopy == nil)). If it doesn't (ie, it's equal to nil) we can assume the object doesn't implement either mutableDeepCopy or mutableCopy, so we instead call a plain old copy and assign its value to oneCopy.
Add oneCopy into our returnDict dictionary using the original key.
That's the for loop - at the end of it all, we go and return the copied dictionary.
The logic in the for-loop is convoluted because the author is trying to get as mutable and as deep a copy of the entire array as possible. The code tries three different ways to satisfy this, in order of preference:
Use mutableDeepCopy if possible (if the object understands that message).
Otherwise, use mutableCopy if possible.
If all else fails, just use copy.
If the object is just plain not copiable, your code goes boom when it sends the object -copy, since no test is made for whether the object responds to -copy. This is appropriate, since trying to deep copy an array containing items that cannot be copied is definitely programmer error.

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

iPhone - Registering an Array containing NSNulls with NSUserDefaults

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!

how to add nil to nsmutablearray?

NSArray *array = [[NSArray alloc] initWithObjects:#"ΕΛΤΑ",
#"ΕΛΤΑ COURIER", #"ACS", #"ACS ΕΞΩΤΕΡΙΚΟ",
#"DHL", #"INTERATTICA", #"SPEEDEX",
#"UPS", #"ΓΕΝΙΚΗ ΤΑΧΥΔΡΟΜΙΚΗ", #"ΜΕΤΑΦΟΡΙΚΕΣ ΕΞΩΤΕΡΙΚΟΥ", nil];
This is working because it has nil at the end.
But I add objects like this: addObject:name etc...
So at the end I have to add nil I do this addObhect:nil but when I run the app it still crashes at cellForRowAtIndexPath:
how can I do this work?
Ok, I dont have to add nil
What is the reason that my app crashes then?
If you must add a nil object to a collection, use the NSNull class:
The NSNull class defines a singleton object used to represent null values in collection objects (which don’t allow nil values).
Assuming "array" is of type NSMutableArray:
....
[array addObject:[NSNumber numberWithInt:2];
[array addObject:#"string"];
[array addObject:[NSNull null]];
You don't need to call [addObject:nil]
The nil in initWithObjects: is only there to tell the method where the list ends, because of how C varargs work. When you add objects one-by-one with addObject: you don't need to add a nil.
You can't add nil when you're calling addObject.
If you really want a Null-ish item in your collection, NSNull is there for that.
You need to add NSNull class and the best way to do it is like this:
NSArray *array = #[ #"string", #42, [NSNull null] ];
I personally recommend to use a specific value like 0 instead of null or nil in your design of your code, but sometimes you need to add null.
There is a good explanation from this Apple reference.
nil is used to terminate the array
nil is not an object that you can add to an array: An array cannot contain nil. This is why addObject:nil crashes.
pass your object through this method when adding to array to avoid attempt to insert nil object from objects crashes.
-(id) returnNullIfNil:(id) obj {
return (obj == nil) ? ([NSNull null]) : (obj);
}
[NSNull null] returns an null object which represents nil.
You can't add an object to an NSArray because that class is immutable. You have to use NSMutableArray if you want to change the array after it is created.