I am trying to understand copying objects from one NSMutableArray to another. Consider the following 2 scenarios:
1 - copying original to clone where changes in the clone will affect the original.
2 - copying original to clone where the changes in the close will NOT affect the original.
First, I am trying to produce scenario #1 first with the following code. From what I understand, when copying array not using 'mutablecopy', the clone array will just hold the pointer to the same string objects in the original. So if I were to change the first element of the clone to a different object, the first element of the original would change too right? ... but that's not the result I am getting. Why?
Matter of fact, when I use mutablecopy
[self.cloneArray addObject:[[self.originalArray objectAtIndex:i] mutableCopy]];
I get the same result. I am confused.
ArrayClass.h
#interface ArrayClass : NSObject {
NSMutableArray *_originalArray;
NSMutableArray *_cloneArray;
}
#property (nonatomic, retain) NSMutableArray *originalArray;
#property (nonatomic, retain) NSMutableArray *cloneArray;
ArrayClass.m
#synthesize originalArray = _originalArray;
#synthesize cloneArray = _cloneArray;
_originalArray = [[NSMutableArray alloc] initWithObjects: #"one", #"two", #"three", #"four", #"five", nil];
_cloneArray = [[NSMutableArray alloc] initWithCapacity:[self.originalArray count]];
for (int i=0; i<5; i++) {
[self.cloneArray addObject:[self.originalArray objectAtIndex:i]];
}
// make change to the first element of the clone array
[self.cloneArray replaceObjectAtIndex:0 withObject:#"blah"];
for (int n=0; n<5; n++) {
NSLog(#"Original:%# --- Clone:%#", [self.originalArray objectAtIndex:n], [self.cloneArray objectAtIndex:n]);
}
...
2011-03-27 03:23:16.637 StringTest[1751:207] Original:one --- Clone:blah
2011-03-27 03:23:16.638 StringTest[1751:207] Original:two --- Clone:two
2011-03-27 03:23:16.639 StringTest[1751:207] Original:three --- Clone:three
2011-03-27 03:23:16.642 StringTest[1751:207] Original:four --- Clone:four
2011-03-27 03:23:16.643 StringTest[1751:207] Original:five --- Clone:five
You are thinking about this way too hard.
In Objective-C, you have references to objects. An NSString *foo; simply defines a variable foo that refers to an NSString. If you say NSString *bar = foo;, then bar will have a reference to whatever object foo was referring to. No more, no less.
An NSArray is just a collection of object references. So, if you say:
NSArray *b = [NSArray arrayWithArray: a];
You are creating an array b that contains all of the same references to the exact same set of objects as a. If you modify an object referred to by a, that'll be the exact same object in b and the modification will be reflected.
When you copy an object, you are creating a new object that has the identical internal state as the original. I.e. when you say NSMutableString *foo = [barString mutableCopy];, then foo is a reference to a new string; a different one than barString.
So... when creating a new array, the question is do you want the array to contain the exact same contents as the original array or do you want it to contain a new set of objects that you can modify?
You have a misunderstanding of what's going on. The replaceObjectAtIndex:withObject: call isn't modifying objects in the array, it's modifying the array itself. After this line:
[self.cloneArray replaceObjectAtIndex:0 withObject:#"blah"];
you've replaced the object in your clone array, but you haven't changed the original array at all. If you actually modified the NSString object you put in the arrays, you might be able to get the behaviour you were expecting. You won't be able to do it with the objects you've put into the original array in your example, though, since they're immutable string objects. If you stuck mutable strings in there, used the same loop to 'clone' your array, and then did something along the lines of:
[[self.cloneArray objectAtIndex:0] appendString:#"some junk to append"];
you would actually modify the string object at index 0. Since both arrays still contain that same object, you'd get the 'modify original array by changing the objects in the clone array' behaviour.
Related
So, when I modify things inside of an NSMutableArray I don't get the result I expect. I think the best way to frame this question is with an example. The following code prints "george" (as expected):
NSMutableArray *originalArray = [[NSMutableArray alloc] initWithObjects:#"sally",#"george", nil];
NSMutableArray *secondArray = [[NSMutableArray alloc] init];
[secondArray addObject:originalArray[1]];
secondArray[0] = #"priscilla";
NSLog(#"%#",originalArray[1]);
But this code prints "priscilla":
TestClass *test1 = [[TestClass alloc] init];
test1.clientName = #"sally";
TestClass *test2 = [[TestClass alloc] init];
test2.clientName = #"george";
NSMutableArray *originalArray = [[NSMutableArray alloc] initWithObjects:test1,test2, nil];
NSMutableArray *secondArray = [[NSMutableArray alloc] init];
[secondArray addObject:originalArray[1]];
TestClass *objectTakenFromSecondArray = secondArray[0];
objectTakenFromSecondArray.clientName = #"priscilla";
NSLog(#"%#", ((TestClass *)originalArray[1]).clientName);
I thought that addObject: always copied the object before adding it to the array receiving the addObject: message. Is this not the case?
Thanks!
p.s. here is the interface and implementation for TestClass in case it is pertinent:
#interface TestClass : NSObject
#property (strong,nonatomic) NSString *clientName;
#end
#implementation TestClass
#synthesize clientName = _clientName
#end
I thought that addObject: always copied the object before adding it to the array receiving the addObject: message. Is this not the case?
addObject: does not copy the object. NSArray does not require that its contents even be copyable (not everything is). That probably explains the confusion. If you want to copy it, you need to do so yourself.
You pretty much answered your own question. When you create an NSMutableArray and add an object to it, you are just creating a pointer to that object, wherever it is stored. If you add the same object to another NSMutableArray, that too contains a pointer to the same thing. You might not need the analogy, but for anyone else confused - the NSMutableArray is like a postman with an address to post to, and the object is the house at that address. Two postmen (or two arrays) can have an address for the same house, but there is only one house still. (That is, unless someone explicitly 'copies' the house).
So in your second to last line of code, where you change that .clientName property, you are changing the property of the original *test2 object.
Worth noting in this case, that if you remove that second array, you don't remove the objects it contains necessarily. So in your case, removing that second NSMutableArray from memory does not mean that all of its objects also disappear from memory - unless everything else that points to those objects also is removed. The array does not contain pointers to unique copy of those objects - it just points to the originals.
I have a mutable array (downloadQueue) containing custom objects (AssetNode) and each object has an NSMutableDictionary as a property.
#interface AssetNode : NSObject {
NSMutableDictionary* allData;
}
#property (nonatomic, retain) NSMutableDictionary* allData;
When I remove an AssetNode from the downloadQueue, the .allData property for the remaining AssetNode objects somehow gets modified and ends up missing some value/key pairs.
-(void)removeAssetNodeFromQueue:(AssetNode*)aNode{
NSMutableArray *temp = [NSMutableArray array];
for (AssetNode* node in downloadQueue)
{
if ([aNode.nodeId isEqualToString:node.nodeId])
{
[temp addObject:node];
}
}
[downloadQueue removeObjectsInArray:temp];
}
Any idea why this happens or how to maintain the integrity of the dictionary info?
Add an NSLog or breakpoint at every point in your code where allData is modified. Log the entire dictionary. I would expect that you have a bug in your code, rather than the NSMutableDictionary accidentally losing your data.
Yeah, retain the dictionary of the object being removed
if ([aNode.nodeId isEqualToString:node.nodeId])
{
[temp addObject:node];
[temp.allData retain];
}
But I suggest storing the allData into another variable, otherwise you'll be leaking memory.
Check out apples Automatic Reference Counting (ARC), this doc is nice:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmRules.html#//apple_ref/doc/uid/20000994-BAJHFBGH
Comment on my answer if you'd like for me to quickly explain retain count to you.
Cheers
I am looking for some clarification on initializing variables in Objective C.
Say I have a method that returns an array.
-(NSMutableArray *) getArray
{
NSMutableArray *arr = [[NSMutableArray alloc]init]; //line A
for(int i = 0; i < 10; i++)
{
[arr addObject:#"word"];
}
return arr;
}
And then I call this method.
NSMutableArray *myArray = [[NSMutableArray alloc]init]; //line B
myArray = [self getArray];
So should I be allocating memory in both lines A and B, neither, or in just A or just B? The alternative being simply
NSMutableArray *arr; //replacing line A
NSMutableArray *myArray; //replacing line B
For any one array, you should be allocating its memory and initializing it once.
To start with, that means that your alternative at the end doesn't work. It's declaring that these two variables exist and will point to arrays but does nothing to create and assign them.
Line B creates and initializes an array just fine, but then immediately loses its only reference to it by assigning the result of getArray to the same variable. If you use ARC memory management, that's a bit wasteful; without ARC, it's a memory leak.
Line A also creates and initializes an array correctly and, as far as the code you've posted goes, that's the one that gets affected by whatever you do next to myArray.
(Remember that the things you declare as variables -- like NSMutableArray *arr -- can be thought of as names for the actual objects rather than objects themselves.)
You should allocate memory in A, but in B.
The alloc/init in A creates the object that the variable arr is refering to. You pass that object as the return value of method getArray.
In B, you simply want myArray to refer to the returned object, you don't want to have a new object. If you do:
NSMutableArray *myArray = [[NSMutableArray alloc]init]; //line B
myArray = [self getArray];
the first line creates a new object and assigns that to myArray. The second line replaces that value of myArray immediately with another object that is returned from the method. Without ARC (automated reference counting), this will lead to a memory leak of the object created in the first line.
I want to use NSMutableDictionary to cache some data i will use later. My custom object is following:
#interface MyData : NSObject {
NSRange range;
NSMutableArray *values;
}
#property (nonatomic, retain) NSMutableArray *values;
and implement:
- (id)init {
if (self = [super init]) {
values = [[NSMutableArray alloc] init];
}
return self;
}
and when i wanna cache it, i use it like this:
NSMutableDictionary *cache = [[NSMutableDictionary alloc] init];
NSString *key = #"KEY";
MyData *data = [[MyData alloc] init];
// save some data into data
[data.values addObject:"DATA1"];
[data.values addObject:"DATA2"];
//... ...
[cache setObject:data forKey:key];
My questions is the count of cache.values is zero when i retrieve this object later as follow:
[cache objectForKey:#"KEY"];
i can retrieve "data" and the object's memory address is the same as the address when i put it into cache.
what's wrong? i need some kind guys help, any info is helpful. thanks
As Carl Norum pointed out, you're passing C strings to addObject:. addObject:, as its name suggests, requires a pointer to a Cocoa object; a C string is a pointer to characters. You need to pass NSString objects there; for literal strings, this simply requires prefixing them with #: "Fred" is a constant C string, whereas #"Fred" is a constant NSString object.
Is cache an instance variable? It looks like it's not; it appears to be a local variable, which means you're creating a new dictionary object every time. That's why there's nothing you've added previously (to previous dictionaries) in the new one. It also means you're leaking those previous dictionaries, since you're not releasing them (not in the code you showed, anyway).
Make cache an instance variable and only create the dictionary when you don't already have one (i.e., when cache == nil). Creating the dictionary in your init method is one good way. And make sure you manage its lifetime appropriately, so you don't leak and/or crash.
First of all your objects your adding don't look right it should have an # before the string. Like #"DATA1"
Second when you add an object to a dictionary or an array it does not make an actual copy of it. It just creates a pointer to it so if those objects are destroyed or moved somewhere also they are also gone out of your dictionary. A better way to make a cache of your values would be to copy the objects like so:
MyData* cache = [[MyData alloc] init];
for (int i = 0; i < [data.values count]; i ++){{
[cache.values addObject:[NSString stringWithString:[data.values objectAtIndex:i]]];
}
Don't use a dictionary in this situation.
I read that non mutable data types can't be modified once created.(eg NSString or NSArray).
But can they be re-initialized to point to a different set of objects?
If so, do I use release to free any alloc from first time round in between uses? eg:
myArray declared as NSArray *myArray in interface, and as nonatomic/retain property.myArray set in initialization code to a point to an array of strings as follows.
self.myArray = [myString componentsSeparatedByString:#","];
But later I want to re-initialize myArray to point to a different set of strings
self.myArray = [myOtherString componentsSeparatedByString:#","];
Is it possible? Thanks...
It really depends what you mean with re-initialize. You can assign another immutable object to a pointer, because the pointers aren't constant.
Example:
#interface MyObj : NSObject {
NSString *name; // not needed in 64bit runtime AFAIK
}
#property(retain) NSString *name; // sane people use copy instead of retain
// whenever possible. Using retain can
// lead to some hard to find errors.
#end
/* ... another file ... */
MyObj *theObject = [[[MyObj alloc] init] autorelease];
theObject.name = #"Peter";
NSString *oldName = theObject.name;
NSLog(#"%#", theObject.name); // -> Peter
NSLog(#"%#", oldName); // -> Peter
theObject.name = #"Martin";
NSLog(#"%#", theObject.name) // -> Martin
NSLog(#"%#", oldName) // -> Peter
If the behavior above is what you want, that's fine.
If you want that last line to return Martin you're in trouble. Those are constant strings and are not meant to be modified. You could, if you really want, modify the memory of the object directly, but this is dangerous and not recommended. Use mutable objects if you need such behaviour.
Yes you can reinitialized the NSArray. Here is the sample code that i used to re-initialized the NSArray.
NSString *keywords = #"FirstName|LastName|Address|PhoneNumber";
NSArray *arr = [keywords componentsSeparatedByString:#"|"];
NSLog(#"First Init - %#,%#,%#,%#",[arr objectAtIndex:0],[arr objectAtIndex:1],
[arr objectAtIndex:2],[arr objectAtIndex:3]);
arr = nil;
keywords = #"First_Name|Last_Name|_Address|_PhoneNumber";
arr = [keywords componentsSeparatedByString:#"|"];
NSLog(#"Second Init - %#,%#,%#,%#",[arr objectAtIndex:0],[arr objectAtIndex:1],
[arr objectAtIndex:2],[arr objectAtIndex:3]);
Of course they can. Saying that an NSArray is immutable doesn't mean that an attribute of a class of that type cannot be changed. You can't change the content, but you can assign new content to it.
If you want to make also changing the reference impossible you should use const keyword.