Assigning, mutable to immutable array? - iphone

Would someone be so kind as to confirm that I am understanding this correctly. My initial response to this was that the mutableArray when assigned to an immutableArray would become an immutableArray, however this is not the case.
Is this because the initial array is allocated as a mutableArray and the immutableArray is just assigned to point to the same object. The compiler gives a warning, but the the code executes just fine at runtime.
NSMutableArray *mArray = [NSMutableArray arrayWithObjects:#"Teddy", #"Dog", nil];
NSArray *iArray = mArray;
[iArray addObject:#"Snoss"]; // Normally this would crash NSArray
much appreciated
gary.

Don't confuse the physical object that you just created, with how you are effectivley casting it in your code.
In this case, you did physically create a NSMutableArray.
Then you effectivley cast it to an NSArray - which is totally valid - for example, there are many cases where a function might want an NSArray, and you can pass it an NSArray, or anything derived from it (like an NSMutableArray).
The problem is that you get a compiler warning because you are trying to call addObject on an object which the compiler thinks is just an NSArray because that's what it's physical type is.
This will actually work, because it actually is an NSMutableArray, and in runtime would respond to that selector.
It's bad coding practice to do it this way, because NSArray doesn't actualy respond to the addObject selector. For example, I could create a function, like:
-(void) addIt:(NSArray)myThing {
[myThing addObject:#"New String"];
}
The compiler would give you a warning saying the "NSArray" doesn't respond to the "addObject" selector. If you cast an NSMutableArray, and passed it to this function, it myThing, it would actually work.
It's bad practice though, because if you actually passed an NSArray it would crash.
Summary: Don't confuse what the object really is vs. what you are making the compiler interpret it as.

Yes, you are right. You should do
NSArray *array = [NSArray arrayWithArray:mutableArray];
to ensure that nobody can change the array

Your iArray is actually referencing to NSMutableArray instance, that's why it is a NSMutableArray.
Obj-c doesn't have strict check on class types, all objects are of type 'id'.
You could write
NSNumber *iArray = mArray
Compiler will show a warning of wrong cast (not error). But it will work.
Don't mess with pointers, there is no object type transformations as you can expect in C++. (there are overloadable operators for casting object to another class).
Obj-c works with objects much like script/interpreted languages. Dynamic typing (objects only), reflection, dynamic change of methods of instance of classes etc - full flexibility. A perfect mix of speed of low-level C/C++ and flexibility of dynamism.

AFAIK, you are correct. NSMutableArray is a subclass of NSArray, so you can assign mArray to iArray here without a problem.
However, it isn't clean code and you should avoid it.

Related

Woes in trapping valueForKeyPath: without a valid path

I have a nested NSMutableDictionary and am successfully pulling out a value at some 'depth' nested in other dictionaries, like:
NSNumber *num = [myDictionary valueForKeyPath:#"league.team.away.score"];
All is good. And I can confirm that all dictionaries at all levels are mutable.
But... what if that key path does not exist?
As expected, I get an NSUndefinedKeyException. I tried a fix with a try/catch tactic, to no avail.
Apple's solution to this concerns overriding valueForUndefinedKey:
"Subclasses can override this method to return an alternate value for undefined keys. The default implementation raises an NSUndefinedKeyException."
Great... so I create a subclass NSMutableDictionaryMod, then I get a complaint...
[NSMutableDictionary initWithCapacity:] method only defined for abstract class. Define -[NSMutableDictionaryMod initWithCapacity:]!
I go define said initializer (and can confirm it gets there in Xcode), but boom it crashes on the self = [super initWithCapacity:numItems]; line.
Then I noticed this Apple gem on NSMutableDictionary:
There should typically be little need to subclass NSMutableDictionary. If you do need to customize behavior, it is often better to consider composition rather than subclassing.
Any suggestions would be appreciated.

CoreData Object typing won't work

Can someone explain to me why this doesn't work:
CoreDataClass *classObject = (CoreDataClass *)[some method that returns a dictionary with exact KVC pairs that match CoreDataClass];
NSString *myString = classObject.stringProperty;
But this does:
CoreDataClass *classObject = (CoreDataClass *)[some method that returns a dictionary with exact KVC pairs that match CoreDataClass];
NSString *myString = [classObject valueForKey:#"stringProperty"];
EDIT:
What's the easiest way to cast the dictionary as my NSManagedObjectClass CoreDataClass so I can access properties directly?
It doesn't work since KVC compliance is not at all what defines classes or makes them castable - the class hierarchy exists for a reason, and just ensuring adherence to certain methods doesn't magically make something an instance of a completely different class. Keep in mind that the dot-accessor syntax is just sugar for a method send, so these two are equivalent:
classObject.stringProperty
[classObject stringProperty]
...and the latter obviously isn't valid for instances of NSDictionary (i.e. [[NSDictionary class] instancesRespondToSelector:#selector(stringProperty)] is NO).
Your latter example works because of the very premise of your question: if something is KVC-compliant for the key stringProperty, and you ask it for a value for that key, then obviously you get something back. Furthermore, both NSDictionary and CoreDataClass respond to the selector -valueForKey:, so the message send actually works at runtime.
The best way to get the two across isn't a "cast" at all - it's a complete conversion, at the property level, of the data involved. You might consider creating a custom -initWith... method on CoreDataClass that lets you instantiate its properties from a dictionary, or finding a way to get your method to return an actual instance of CoreDataClass instead of an NSDictionary.
Note that this solution may differ from the "easiest" way to get the data across, which is effectively to keep doing what you're doing and use -valueForKey: (though preferably without the cast, which is misleading).
Casting objects only appears to work (in the sense that you won't get type-checking errors) because it's a hint to the compiler, but it doesn't actually change anything about what the pointer points to, so you are still pointing to an NSDictionary. This is because, at the end of the day, you are essentially casting a pointer to a pointer, but telling Xcode that you are allowed to send a different set of selectors to it.
For NSManagedObjects, creation from a dictionary depends on a few things, but the recommended way is to make a class method on your custom class which will use NSEntityDescription and you NSManagedObjectContext, and sets the properties from the dictionary to the object:
+(CoreDataClass *) coreDataObjectWithDictionary:(NSDictionary *) spec {
CoreDataClass *myInstance = [NSEntityDescription insertNewObjectForEntityForName: #"CoreDataClass" inManagedObjectContext: [myMOCProvider sharedMOC];
myInstance.someProp = [spec valueForKey:#"someProp"];
}

Sending message to objectForKey to NSMutableArray instance, And it works!! Strange?

I have a very bad code written in my program, was just playing around as I am learning Objective C and iOS platform. What I did is,
I have created NSMutableArray like this,
placeInfo = [NSMutableArray array];
and than later in my code I am doing something like this, basically I am manipulating Google places api response(JSON).
NSDictionary *results = [responseString JSONValue];
placeInfo = [results objectForKey:#"result"];
self.phoneNumber = (NSString *)[placeInfo objectForKey:#"formatted_phone_number"]; // In this line compiler warns me that NSMutableArray might not response to this.
I checked documentation but I didn't find objectForKey in NSMutableArray.
So what could be the reason? Why my code isn't crashing? Why it is returning phone number by "formatted_phone_number" key?
EDIT
After first answer I have edited my code and added type casting like this, but it still works.
NSDictionary *results = [responseString JSONValue];
placeInfo = (NSMutableArray *)[results objectForKey:#"result"];
self.phoneNumber = (NSString *)[placeInfo objectForKey:#"formatted_phone_number"];
I’ve never used the Google Places API, but I’d guess [results objectForKey:#"result"] actually returns another dictionary, so the objectForKey: works.
Because objective-c just uses pointers to refer to objects, it’s never actually being converted to an NSMutableArray. Also, objective-c doesn't know at compile time if a method will exist, due to its dynamic nature (you can actually add methods and even whole classes at runtime).
Depending on the compiler settings, it may just show a warning that objectForKey: might not be found at runtime, and let it continue compiling anyway. It ends up working just fine if you actually passed it an NSDictionary.
Even when you put the (NSMutableArray *) cast in front of it, it won’t change anything. That simply casts the pointer type, and doesn’t actually change the object in any way.
It's doing this because [results objectForKey:#"result"] is returning you something that is not an NSMutableArray. That something that's being returned is likely another NSDictionary which, of course, does respond to objectForKey: To find out what you've got, set a breakpoint after result = [placeInfo objectForKey:#"result"] and inspect result. The debugger will tell you what kind of object you're dealing with. I'll bet you anything you like that it's an NSDictionary.
Objective C allows you to send any message (called a selector) to any object at any time; the runtime does not care whether a particular object implements a given selector. If the target object does not respond to a given selector it will ignore it. It will not crash. In this respect it's utterly unlike most other OOP languages, including C++ and Java, which will fail to compile if you try to call a method that a particular class doesn't implement. You can find out if an object responds to a given selector (which is analagous to using introspection to see if a given class implements a certain method) by saying [result respondsToSelector:#selector(objectForKey:)]. This difference between methods and messages is critically important to understanding Objective C. I'd recommend reading The Objective C Programming Language before doing anything else.
Also, Objective C's type system is less stringently enforced than those other languages. It's quite legal (although a very bad idea) to do what you have done here, which is to declare a pointer of type NSMutableArray and then assign it to (I'm guessing) an NSDictionary.

Do I need to initialize an iOS empty nested array that's part of a plist import?

the code below is working, but I want to make sure it's correct. I'm nervous about having an empty Array inside my dictionary that I create from the plist, since typically it seems that if you don't, say, initWithCapacity:1 then you often get memory errors once you start trying to add items.
At least, that's been my experience with NSMutableDictionary. However, this is the first time I'm trying to implement nested data objects, so perhaps the reason this code works is that the nested array is automatically initialized when it's imported as part of its parent dictionary?
Any and all comments appreciated. Thanks.
First, here's what the plist looks like that I'm using to create my dictionary:
Next, here's my code where I'm using the plist to create a dictionary, then adding an item to dataArray
// Create a pointer to a dictionary
NSMutableDictionary *dictionary;
// Read "SomeData.plist" from application bundle
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"SomeData.plist"];
dictionary = [NSMutableDictionary dictionaryWithContentsOfFile:finalPath];
// Now let's see if we can successfully add an item to the end of this empty nested array. How 'bout the number 23
NSNumber *yetAnotherNumber = [NSNumber numberWithInt:23];
[[dictionary objectForKey:#"dataArray"] addObject:yetAnotherNumber];
// Dump the contents of the dictionary to the console
NSLog(#"%#", dictionary);
Okay, fine, simple, good. When I Log the dictionary contents it shows that "23" has been added as an array value to dataArray. So the code works. But again, I want to confirm that I'm not "getting lucky" here, with my code just happening to work even though I'm not properly initializing that nested array. If so, then I could run into unanticipated errors later on.
So to sum up, dataArray is an empty array inside the .plist, so do I need to initialize it somehow (using, for example initWithCapacity: or something else) before I can properly populate it, or is the way I'm coding here just fine?
Thanks again.
EDIT
Hey all. I've been doing continued research on this, in the interests of finding a satisfying answer. I think I may have stumbled upon something, via this link on deep copying. His previous posts on deep copying had presented some code to do essentially what I was looking for above: create a mutable copy of a dictionary or array, from a plist, that also has mutable sub-structures.
However, as mentioned in the link above, it looks like these methods were superfluous, due to the CFPropertyListCreateDeepCopy method, which can be invoked with a call such as
testData = CFPropertyListCreateDeepCopy(kCFAllocatorDefault, [NSDictionary dictionaryWithContentsOfFile:path], kCFPropertyListMutableContainersAndLeaves);
So, my question is, can I properly use CFPropertyListCreateDeepCopy, in the way shown, to achieve what I've been asking about here? In other words, can I use this method to import my dictionary from a plist with fully mutable, nested data objects?
As I mentioned in one of the comments, I know I can create a nested, mutable dictionary manually, but for complex data that's just not practical, and it seems unlikely that built-in methods to import a mutable plist don't exist. So, based on the above, it looks like I've possibly found the solution, but I'm still too new to this to be able to say for sure. Please advise.
(Side note: I would simply test the code, but as we've established, the current SDK is buggy with regard to allow you to edit immutable nested dictionaries, contrary to the documented behavior. So as before, I'm not just interested in whether this works, but whether it's correct)
Thanks in advance.
init... methods should only be called once, immediately after a call to alloc or allocWithZone:. When framework code creates and returns an object or graph of objects, their init... methods have already been called, so sending another init... message would have undefined results. Don't do that.
Interestingly, in spite of what the documentation appears to say (and admittedly I probably missed a key sentence or paragraph somewhere), when you create an instance of a mutable collection by reading a plist, any nested collections are also mutable. I ran the following little experiment in a test harness just to be sure:
NSMutableDictionary *pets = [NSMutableDictionary dictionaryWithContentsOfFile:#"/tmp/Pets.plist"];
NSMutableArray *cats = [pets objectForKey:#"cats"];
[cats addObject:#"Foo"]; // EDIT: Added line I accidentally omitted earlier
NSLog(#"%#", cats);
So again, the nested collections created when you read in the plist are fully initialized, and mutable to boot, so you can simply use them, as you've been doing.
EDIT
However, after doing some further reading of the docs, I think the OP is right to feel uneasy about relying on what is apparently an undocumented feature of the current version of the SDK. For example, the Property List Programming Guide states:
If you load the property list with
this call:
NSMutableArray * ma = [NSMutableArray arrayWithContentsOfFile:xmlFile];
ma is a mutable array with immutable
dictionaries in each element. Each key
and each value in each dictionary are
immutable.
So, to be on the safe side, if you need a nested collection to be mutable, you should create it yourself. For example, I'd recommend rewriting the code in the example above as follows:
NSMutableDictionary *pets = [NSMutableDictionary dictionaryWithContentsOfFile:#"/tmp/Pets.plist"];
NSArray *cats = [pets objectForKey:#"cats"];
NSMutableArray *mutableCats = [cats mutableCopy];
[pets setObject:mutableCats forKey:cats];
[mutableCats release];
You can then safely make changes to the nested mutable collection:
[mutableCats addObject:#"Foo"];
Any object in a dictionary which is created by reading from disk will be properly initialized. You will not have to do it on your own. However, as pointed out by jlehr, contents of the dictionary should be immutable. If you want the contents of the dictionary to be mutable, you will need to change them on your own. I have no idea why your program is not throwing an exception.
I do not know why you are getting memory errors while not using initWithCapacity:1 in other situations. The following code is perfectly valid:
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:#"object1"];
[array addObject:#"object2"];
NSLog(#"%#",array);
[array release];
If you don't specify a capacity, the array won't pre-allocate any memory, but it will allocate memory as required later.
Edit:
It is perfectly acceptable to use NSDictionary with CFPropertyListCreateDeepCopy. In Core Foundation, a CFPropertyList can be a CFDictionary, CFArray, CFNumber, CFString, or CFData. Since NSDictionary is toll-free bridged to CFDictionary, you can use it wherever a CFDictionary is asked for by casting, and vice-versa. Your code as is will give a warning, but you can suppress it by casting the dictionary and return values.
NSDictionary *testData = (NSDictionary*)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)[NSDictionary dictionaryWithContentsOfFile:path], kCFPropertyListMutableContainersAndLeaves);

Passing NSMutableArray into a function

I have this problem with Cocoa, I am calling a function and passing an Array to it:
Some where I call the function:
[self processLabels:labels];
And the function is as follow:
- (void)processLabels:(NSMutableArray*)labs{
labs = [[NSMutableArray alloc] init];
[labs addObject:#"Random"];
....
}
When debugging, I notice that no new object are being added to labels when they are added to labs. Is it because I am re-initializing labs? how could I re-initialize labels inside the function then?
I tried using byref by didn't succeed,
any help is appreciated..
thanks
'labs' should be initialized before you pass it to processLabels, and then it shouldn't be re-initialized.
If you can't initialize the array beforehand for whatever reason and you want processLabels to create it, you need to pass a pointer to a pointer:
[self processLabels:&labels];
and the method would change to:
- (void)processLabels:(NSMutableArray**)labs{
*labs = [[NSMutableArray alloc] init];
[*labs addObject:#"Random"];
....
}
generally spoken, it is preferrable to not pass mutable collections, but to provide methods which perform work on them...
in fact, in response to your code I wonder even what the purpose is of passing the 'labs' array into the function when in fact you are just overwriting it (and creating a memory leak in the process). why do that?
The statement labs = [[NSMutableArray alloc] init]; makes labs to point to the new array in the scope of the method. It does not make the caller's pointer point to the new array.
If you want to change the caller's pointer, do something like this:
// The caller
NSMutableArray *labels; // Don't initialize *labels.
[self processLabels:&labels];
...
- (void)processLabels:(NSMutableArray**)labs{
*labs = [[NSMutableArray alloc] init];
[*labs addObject:#"Random"];
...
}
That's probably a bad idea because processLabels: allocates the array but the caller is responsible for freeing it.
If you want the caller to own the array, you could write processLabels: like this:
- (void)processLabels:(NSMutableArray*)labs{
[labs removeAllObjects];
[labs addObject:#"Random"];
...
}
Or, if processLabels: is just returning a collection of labels:
- (NSMutableArray*)processLabels {
NSMutableArray* labs = [[[NSMutableArray alloc] init] autorelease];
[labs addObject:#"Random"];
...
return labs;
}
If you want the caller to be responsible for freeing the array, remove the autorelease. In that case, convention dictates that the method name should start with alloc or new, or contain the word copy.
You need to pass in a mutable array to be able to change it (that's the definition of Mutable) - to turn an NSArray into a mutable array, use:
NSMutableArray *writableArray = [NSMutableArray arrayWithArray:oldArray];
or if you just want to make an empty mutable array:
NSMutableArray *writableArray = [NSMutableArray array];
Then pass that in.
Will is correct, both about correcting the existing method, and about it being a bad idea. Storing back to a by-reference parameter is certainly valid, and it's used frequently in plain C programs, but in this case it adds needless complexity. In Objective-C, the preferred idiom is to return objects using the return value first, and only store back to a pointer if the return value is already being used to return something else. Not only will this make calls to a method easier to read and write, but it conforms to standard idioms that are commonly used in other languages (such as Java and C#). It becomes quite obvious if you overwrite an array pointer by assigning to it, a potential bug that is more likely to be picked up by tools like the Clang Static Analyzer.
On a related note, you should probably also consider better method and parameter naming. (I realize this is likely a somewhat contrived example.) If you're processing "labels", and they come from some source other than the mutable array you're creating, I wouldn't name the local variable "labs" or "labels" — use a more descriptive name. Method names that are less vague about what they do can vastly improve code readability. In Objective-C, long descriptive method names are preferred. Since Xcode does code completion and the method names are less ambiguous, the end result is usually less typing.