This question already has answers here:
Immutable/Mutable Collections in Swift
(7 answers)
Closed 5 years ago.
I don't have any problem, i would just like some clarification on an issue regarding mutability.
In Objective-C we would use for example NSMutableArray to get a mutable array and an NSArray to get an immutable one. I don't know much about the inner workings of the two, but from what I can remember I believe that the difference is that NSArray only reserves an amount of memory specific to the initial value which makes it more efficient, while NSMutableArray has no idea how much memory it will require. Presumably this means that NSMutableArray has pointers to bits of memory that are all over the place and not one by one like with NSArray? Or does it perhaps just reserve a lot of memory hoping it won't run out?
In Swift the obvious substitution is let for immutable and var for mutable. If a variable is declared with these keywords that I see no difference between Swift and Objective-C. However, I don't understand how it works if I declare the variable without the var/let by, for example, storing it in another variable.
Let's say I have a dictionary such as [String: [String]]. In other words, for each string key there is an array of strings. Consider the following case:
var dictionary: [String: [String]] = [:]
dictionary["key"] = ["string0", "string1"]
//dictionary now is ["key": ["string0", "string1"]]
But what is the strings array now? Is it mutable because the dictionary is mutable? Is it mutable because everything we assign is mutable? How about the following case:
let dictionary = ["key": ["string0", "string1"]]
dictionary["key"].append("string2")
Would this work?
I guess the key issue is that in Objective-C I always define whether am I working with NSMutableArray or NSArray. Creating an array using the literal syntax [#"string"] always leads to an NSArray and it won't be mutable unless I specify it. In Swift I don't know when is what mutable.
Thanks
For arrays and dictionaries, the let or var keyword decides whether the whole collection would be mutable or immutable. In other words, if you declare a dictionary immutable by using the let keyword, you cannot change any of its values, so the second example would not work.
In Swift deciding whether a collection will be mutable or immutable only depends on the keyword you use to declare it, so declaring an array/dictionary using the let keyword will be equivalent to declaring an immutable array (NSArray in Objective-C) while declaring it with the var keyword will give you a mutable array (NSMutableArray in Objective-C).
If you create an array, a set, or a dictionary, and assign it to a
variable, the collection that is created will be mutable. This means
that you can change (or mutate) the collection after it is created by
adding, removing, or changing items in the collection. If you assign
an array, a set, or a dictionary to a constant, that collection is
immutable, and its size and contents cannot be changed.
So, let means constant, if you declare array or dictionary using let it will be immutable.
if you declare array or dictionary as var it will be mutable.
So, the case below will not work because dictionary will be immutable:
let dictionary = ["key": ["string0", "string1"]]
dictionary["key"].append("string2")
Check the reference here:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/CollectionTypes.html
Related
I'm confused by the code, below. Before I added the mutableCopy line, it didn't work. After I added the line, it did.
Why isn't aDict mutable to begin with? I declared aDict as an NSMutableDictionary.
- (void) myRoutine: (NSMutableDictionary *) dictOfDicts
{
NSMutableDictionary * aDict = dictOfDicts[dictOfDictsKey];
int data = [aDict[aDictKey] intValue];
aDict = [aDict mutableCopy];
aDict[aDictKey] = #(++data);
}
The declaration of dictOfDicts says it's a pointer to a mutable dictionary. However, it does not use Objective-C generics syntax to say what the types of the keys or values are. So, the most we (and the compiler) can assume is that the keys are id<NSCopying> and the values are id, completely generic object pointers.
You then initialize your aDict variable with a value obtained from dictOfDicts. You've declared that aDict is also a pointer to a mutable dictionary. That's its "static type", but the real type of the object it points to is determined at runtime by whatever object is held in dictOfDicts under that key. It might be a mutable dictionary or it might be something else. It compiles just find because the compiler can't know what type of object that value is.
However, the real type (a.k.a. "dynamic type") of the object governs what operations succeed or fail/crash/whatever. In your case, it sounds like it's an immutable dictionary. So, when you attempt to mutate it, "it didn't work" (you don't specify what actually happened).
You make a mutable copy and you're allowed to mutate that. However, that's now a separate object that the one in dictOfDicts. So, you're not modifying what you think you are.
The solution is to put mutable dictionaries into dictOfDicts in the first place. Or, even better, put objects of a custom class of your own design into it, and operate on real properties.
I'm quite blank when it comes to swift, I've been developing using Obj-c. But a tutorial that I've been following uses Swift. Can anyone help me convert the following line of Swift into Objective-C. It's basically to load a String onto an Array.
self.iDArray.append(objectIDs[i].valueForKey("objectId") as! String)
self.iDArray.append(objectIDs[i].valueForKey("objectId") as! String)
Should be
[self.iDArray append: [objectIDs[1].valueForKey: #"objectID"]]
However, the Swift code is force-casting [objectIDs[1].valueForKey: #"objectID"] to type String (A Swift string).
That suggests to me that self.iDArray may be a Swift array. Swift arrays normally contain only a single type. You create an array of String objects, or an array of Dictionary objects. You can also create an array of AnyObject.
NSArray is an array of id type.
I'm not 100% positive how to force-cast to String type in Objective-C. maybe:
[self.iDArray append: (String) [objectIDs[1] valueForKey: #"objectID"]]
On the surface, objectIDs[x] appears to be a dictionary, and the compiler will give you a break on types if you dereference it that way. So naive to parse, a usable syntax would be:
[self.iDArray append:objectIDs[1][#"objectId"]];
But that's incorrect semantically for parse, since the implication is that the objectIDs array is implied to contain parse objects (named confusingly with the "IDs" suffix). If it's really parse objects, then the collection style reference for objectId won't work, and should be instead
[self.iDArray append:((PFObject *)objectIDs[1]).objectId];
Or more readably:
PFObject *object = objectIDs[1];
NSString *objectId = object.objectId;
[self.iDArray append:objectId];
But, along the same lines semantically, the implication of the code is that it's adding to an NSMutable array, so it probably should be -- for any of the above suggestions:
[self.iDArray addObject: .....
Stop reading here if you care only about compiling and executing without a crash.
But, even if all that's right, which I think can be inferred from the code, it's indicative of bad design in my opinion. Swift developers in particular seem to have a penchant for saving off objectIDs and passing them around as proxies for object, and in so doing, loosing all of the other valuable stuff in the PFObject.
My practice is, wherever possible, just keep and pass the whole PFObject. You can always ask it for its objectId, later. More strongly, my rule of thumb when reading code is: show me parse.com code that refers much to objectIds -- except for things like equality tests -- and I'll show you a design error.
I have a time sensitive section of my code which stores, retrieves, and replaces values in an NSMutableDictionary. The problem is that I need to store objects, not primitives, and objects need to be allocated on the heap. However, once there exists an object in the dictionary and I want to replace the value, instead of allocating another number, can't I simply replace the current heap literal, wherever it may be, with the new int?
Problem is, I have been storing NSNumbers and I cannot change the value of an NSNumber because they are immutable.
Currently, I use the #() wrapper operator to create an NSNumber, which I believe must be copied to the heap to be stored in the dictionary.
-(void)setInt: (int)value For: (NSString *)key {
[self.dictionary setValue:#(value) forKey:key];
}
I would imagine that replacing an object's primitive in C would be easy:
-(void)setInt: (int)value For: (NSString *)key {
SomeMutableIntClass * oldValue;
oldValue = (SomeMutableIntClass *) [self.dictionary objectForKey:key];
oldValue.int = value; // Direct copy like a pointer since int is primitive
}
I am wondering if I should just make an Integer class with one int property. Even if I went about making my own class would this actually be faster and require less allocing that my current code?
I use the #() wrapper operator to create an NSNumber, which I believe must be copied to the heap to be stored in the dictionary.
This is correct.
I would imagine that replacing an object's primitive in C would be easy:
That is correct too. Unfortunately, since NSNumber is immutable, you need to write your own SomeMutableIntClass.
Even if I went about making my own class would this actually be faster and require less allocing that my current code?
Adding mutability may help, but the answer depends on the values that you store in your mutable dictionary. If you keep storing the same numbers over and over, you may be getting cached instances of your wrappers, without additional allocations.
Note: your current program assumes that all keys will be present in the dictionary. If this is not true, you would need to add nil checking of oldValue to your setInt: method.
I'm new to obj-c development but partly have background in C development. It might be a noob question but I couldn't get an exact answer in other places. What is the difference between these snippets for arrays and strings and possibly other types of objects:
NSArray *original = [NSArray arrayWithObjects:someObjects,nil];
//Case 1
NSArray *copy1 = original;
//Case 2
NSArray *copy2 = [NSArray arrayWithArray:original];
and for strings
NSString *original = #"aString";
//Case 1
NSString *copy1 = original;
//Case 2
NSString *copy2 = [NSString stringWithString:original];
If I make changes to copy1 and copy2 later will they be reflected on original objects? And does the same rules apply to other object types?
The second code snippet does for NSString what the first code snippet does for NSArray. There is no difference in the behavior, because both NSString and NSArray objects in Cocoa are immutable.
When you call [NSString stringWithString:original], Cocoa is smart enough not to create a new object: the reasoning behind this decision is that since original cannot be changed, there's nothing you could do to tell apart a copy from the original. Same goes for [NSArray arrayWithArray:original], because you get the same instance back.
Note: If someObjects is mutable, one could tell apart an array from its deep copy by modifying the object, and seeing if it changes in the other place. However, arrayWithArray: method makes a "shallow" copy, so you wouldn't be able to detect a difference even if the objects inside your array are mutable.
Your question is really about what objects pointers are pointing to. When you say make changes to copy1 and copy2 later, I guess you mean to the pointer contents, not to the object referenced by that pointer. This is a rather functional way to think, but it important non-the-less.
In your example, the array / string part doesn't matter, because you aren't doing anything with the objects, you are just doing things with the pointers to those objects.
original points to one object. copy1 points to the same object. copy2 points to a different object (but which, in this case, is a copy of the first object).
copy1 is not a copy, but another pointer to the same memory as original. copy2 is actually a copy, pointing at a different piece of memory.
If you modify copy1 (assuming it was mutable, which you example code is not), you are modifying original too, as they point at the same piece of memory.
If you modify copy2, original should be unchanged (generally speaking). In your array example, the objects in the array original and in the array copy2 are, I believe the same. So you in this case, you have two arrays, but they have in them the same objects.
NSArrays and NSStrings are immutable so you can't change them.
You can't add or remove objects from NSArray, but if you change some object in array, it will change in its copy because NSArray holds a pointer to it.
How do you fill a NSMutableArray with a set capacity for later use?
Basically I want to set up a NSMutableArray to act as a map for my game objects, so I have this line...
gameObjects = [[NSMutableArray alloc] initWithCapacity:mapWidth*mapHeight];
Which I had hoped would create and fill my MutableArray so I can get then access it with this kind of index...
int ii = (cellY*mapWidth)+cellX;
NSDictionary *currentObject = [gameObjects objectAtIndex:ii];
But I just learned initWithCapacity doesn't fill the array, so should I create blank objects to fill it with, or is there a Null that I can fill it with? Also would I do that with 2 for loops or is there an instruction something like "initWith:myObject" ?
I want to be able to check at a certain index within the array to see if there's an object there or not, so I need to be able to acces that index point, and I can only do that if there's something there or I get an out of bounds error.
I'll be using this NSMutableArray pretty much as a grid of objects, it's a 1 dimensional array organised as a 2 dimensional array, so I need to be able to fill it with mapWidth*mapHeight of something, and then calculate the index and do a check on that index within the array.
I've looked on here and googled but couldn't find anything like what I'm asking.
Thanks for any advice.
I think what you are looking for is [NSNull null]. It is exactly what you want- a placeholder value.
You can find more information on the topic in this question.
initWithCapacity is just a performance optimization -- it has no effect on the array behavior, it just keeps the code "under the covers" from having to repeatedly enlarge the internal array as you add more entries.
So if you want a "pre-allocated" array, you'd need to fill it with NSNull objects or some such. You can then use isKindOfClass to tell if the object is the right type, or simply == compare the entry to [NSNull null]. (Since there's only ever one NSNull object it always has the same address).
(Or you could use a C-style array of pointers with nil values for empty slots.)
But you might be better off using an NSMutableDictionary instead -- no need to pre-fill, and if the element isn't there you get a nil pointer back. For keys use a NSNumber object that corresponds to what would have been your array index.
initWithCapacity only hints to NSMutableArray that it should support this many objects. It won't actually have any objects in it until you add them. Besides, every entry in the array is a pointer to an object, not a struct like you'd normally have in a standard c array.
You need to change how you're thinking about the problem. If you don't add an object to the array, it's not in there. So either you pre-fill the array with "empty" objects as you've said, which is weird. Or you can add the objects as you need them.