When you add object into collection, such as NSMutableArray, the object is copied, that is, value semantics, or is retained, that is, reference semantics?
I am confused in the example:
NSMutableString *testStr = [#"test" mutableCopy];
NSMutableArray *arrayA = [[NSMutableArray alloc] init];
[arrayA addObject:testStr];
NSLog(#"%#", arrayA); // output: test
testStr = [#"world" mutableCopy];
NSLog(#"%#", arrayA); // output: test
// testStr is copied - value semantics
NSMutableArray *testArr = [#[#1, #2] mutableCopy];
NSMutableArray *arrarB = [[NSMutableArray alloc] init];
[arrarB addObject:testArr];
NSLog(#"%#", arrarB); // output: [1, 2]
[testArr addObject:#3];
NSLog(#"%#", arrarB); // output: [1, 2, 3]
// testArr is retained - reference semantics
You can see: if the object is a NSMutableString, it looks like the object is copied - you change the object will not affect the object in the array.
However, if the object is a NSMutableArray, when you change the object, the object in the array also be changed - like you retain the object or pass by reference.
Am I missing something here?
Thanks.
Collections retain, not copy, objects that are added to them.
In your first part, adding an object to the array is not making a copy. Let's look at your code:
Here you created your NSMutableString and your NSMutableArray:
NSMutableString *testStr = [#"test" mutableCopy];
NSMutableArray *arrayA = [[NSMutableArray alloc] init];
Then you add your string to the array:
[arrayA addObject:testStr];
NSLog(#"%#", arrayA); // output: test
Next you're creating a new NSMutableString and assigning that to the variable testStr, which simply makes testStr point to the new object. It has no effect on the first string you created:
testStr = [#"world" mutableCopy];
NSLog(#"%#", arrayA); // output: test
So that's why your first block of code works how it does.
In your second block of code, it seems that you already understand why that works how it does, but just to make it clear, testArr always points to the same array, which you also added to arrarB, hence why printing out arrarB reflects the change you make to testArr.
String is retained too, in line:
testStr = [#"world" mutableCopy];
you create a new string and you assign its copy to the testStr local variable, so old retained string is still in the table
Instead of:
testStr = [#"world" mutableCopy];
you could try:
[testStr setString:#"world"];
This would modify 'old' string instead of making testStr to point on a new object
NSMutableString *testStr = [#"test" mutableCopy];
NSMutableArray *arrayA = [[NSMutableArray alloc] init];
[arrayA addObject:testStr];
NSLog(#"%#", arrayA); // output: test
testStr = [#"world" mutableCopy];
NSLog(#"%#", arrayA); // output: test
Here testStr is not copied to arrayA. After adding testStr object to array, testStr pointer itself changed to reference to some other object (lets say "World").
In the same was as you did for array, you can also append another string to testStr (instead of pointing to different string ) you can see the same results as testArr.
[testStr appendString:#" Test 2"];
`NSLog(#"%#", arrayA);` // output: test Test 2
Related
I am trying to grab a relationship property of an object from a mutable array.
theNewItems[0].step is giving the error, Property 'step' found on object of type id.
Here is how I created the array:
NSMutableArray* theNewItems = [NSMutableArray arrayWithCapacity:20];
[theNewItems addObject:_itemsOnLoad[0]];
[theNewItems addObject:_itemsOnLoad[1]];
[theNewItems addObject:_itemsOnLoad[2]];
And here is how the array logs out
<Items: 0x1706842e0> (entity: Items; id: 0xd000000001900004 <x-coredata://AFF50577-0975-4124-AC70-074F355B73A0/Steps/p100> ; data: {
item = "0xd000000016000000 <x-coredata://AFF50577-0975-4124-AC70-074F355B73A0/Dare/p1408>";
sid = 545;
step = "step three";
wasdeleted = nil;
whenadded = nil;
}),
<Items: 0x170684330> (entity: Items; id: 0xd000000001840004 <x-coredata://AFF50577-0975-4124-AC70-074F355B73A0/Steps/p97> ; data: {
item = "0xd000000016000000 <x-coredata://AFF50577-0975-4124-AC70-074F355B73A0/Dare/p1408>";
sid = 544;
step = "step two";
wasdeleted = nil;
whenadded = nil;
}),
<Items: 0x170684380> (entity: Items; id: 0xd000000001780004 <x-coredata://AFF50577-0975-4124-AC70-074F355B73A0/Steps/p94> ; data: {
item = "0xd000000016000000 <x-coredata://AFF50577-0975-4124-AC70-074F355B73A0/Dare/p1408>";
sid = 543;
step = "step one";
wasdeleted = nil;
whenadded = nil;
})
)}
Should I be creating the mutablearray differently? Or how can I grab the property "step"?
Thanks in advance for any suggestions.
For clarification...
An NSArray (mutable or not) can hold objects of any type. So, when you "get" an object from the array, the compiler needs to know what you are getting.
Example:
NSMutableArray *a = [NSMutableArray arrayWithCapacity:40];
[a addObject:[UIView new]]; // add a UIView
[a addObject:#"A string"]; // add a NSString
[a addObject:#100]; // add a NSNumber
You now have an array with a View, a String and a Number. If you try to do this:
UIView *v = a[0];
NSString *s = a[1];
NSNumber *n = a[2];
You'll get warnings because while the types are correct, the compiler doesn't know that.
To actually use the objects you've stored in the array, you have to cast them. So, with the same example data:
UIView *v = (USView *)a[0];
NSString *s = (NSString *)a[1];
NSNumber *n = (NSNumber *)a[2];
is fine... you can use your v s and n objects as you'd expect.
For your specific object type of Items, you could:
Items *thisItem = (Items *)theNewItems[0];
NSString *theStep = thisItem.step;
or, more concisely:
NSString *theStep = ((Items *)theNewItems[0]).step;
In 2015, Apple introduced "Lightweight Generics" into Objective-C. This allows you to declare an array of type:
NSMutableArray <Items *> *theNewItems = [NSMutableArray arrayWithCapacity:20];
[theNewItems addObject:_itemsOnLoad[0]];
[theNewItems addObject:_itemsOnLoad[1]];
[theNewItems addObject:_itemsOnLoad[2]];
NSString *theStep = theNewItems[0].step;
And no more casting. Note that you still add your Items objects to the array in the same manner.
Another note: Reading around you'll find some debate about arrayWithCapacity. The most reliable info I've found explains that it perhaps used to make memory management a bit more efficient, but these days it's simply a "hint" and, really, only makes for readability as in:
"When I review my code, I see that I'm expecting this array to hold 40 objects."
It does not, however, pre-allocate memory... nor does it limit the array to 40 elements - the array will still expand as you continue to add objects to it.
You don't need to use arrayWithCapacity you can just make an array using [[NSMutableArray alloc] init]; which will have no limit on capacity.
To get the property just say ((Items *)theNewItems[x]).step, x being the index at which you want the property. Also if you want to skip the casting step when pulling the object out of the array define your array as NSMutableArray<Items *> * theNewItems = [[NSMutablearray alloc] init] then you can just say theNewItems[x].step
In my app there is huge number of array lists. That's why I have added all arrays in one main array list, and I have initialized them using a "for" loop.
I am getting error inside the "for" loop: "Fast enumeration variables can't be modified in ARC by default".
NSMutableArray * MainArray = [[NSMutableArray alloc] initWithObjects:NameArray, IdArray, MasterIdNameArray, MasterIdArray, nil];
for (NSMutableArray * array in MainArray) {
array = [[NSMutableArray alloc] init];
}
Yes, you can not modify the values of array in a fast enumeration, i.e. for(x in Array). The object x becomes constant, hence it would through a warning.
However you can use for(int i=0; i<[MainArray count]; i++) loop to achieve this.
But, wait: Why you want to initialize it after adding it to an array. Do it like this:
//first create all the arrays that you have,
//NameArray
//IdArray
//MasterIdNameArray
//MasterIdNameArray
//then add them in the MainArray
NSMutableArray *nameArray = [NSMutableArray array];
NSMutableArray *idArray = [NSMutableArray array];
NSMutableArray *masterIdNameArray = [NSMutableArray array];
NSMutableArray *masterIdNameArray = [NSMutableArray array];
NSMutableArray *mainArray = [#[nameArray, idArray, masterIdNameArray, masterIdNameArray] mutableCopy];
Note: I renamed all the variable for the shake for Naming Conventions in Objective-C.
SeanChense is correct. You cannot put an array without initializing it.
NSMutableArray * MainArray = [[NSMutableArray alloc]init];
for (int i = 0;i < YOURCOUNTHERE;i++) {
NSMutableArray * array= [[NSMutableArray alloc]init];
[mainArray addObject:array];
}
If your NameArray is nil, MainArray is nil.
You can do it likes:
NSMutableArray *mainArray = [#[] mutableCopy];
for (int i = 0;i < 3;i++) {
[mainArray addObject:[#[] mutableCopy]];
}
In my app there is huge number of array lists. That's why I have added all arrays in one main array list, and I have initialized them using a "for" loop.
You appear to be misunderstanding how variables and reference types work. Maybe the following will help:
Your line of code:
NSMutableArray *mainArray = [[NSMutableArray alloc] initWithObjects:nameArray, idArray, masterIdNameArray, masterIdArray, nil];
copies the references stored in each of the variables nameArray, idArray etc. and stores those references in a new array.
Somewhere you must declared each of these variables, e.g. something like:
NSMutableArray *nameArray;
This declares a variable nameArray which can hold a reference to a mutable array, that is a reference of type NSMutableArray *. The variable is initialised with the value nil - the "no reference" value.
When your first line of code is executed the value in each variable is passed in the method call, not the variable itself, so the call is effectively:
NSMutableArray *mainArray = [[NSMutableArray alloc] initWithObjects: nil, nil, nil, nil, nil];
and mainArray is set to reference a new mutable array with zero elements - as all references before the first nil in the argument list are used as the initial values in the array.
After mainArray has been setup in this way any operation on it has no effect on the values stored in variables nameArray et al. - there is no connection to those variables. In your loop:
for (NSMutableArray *array in mainArray) {
array = [[NSMutableArray alloc] init];
}
The variable array is a new variable, which is set in turn to each of the values in mainArray. The variable array does not become an alias for each of the variables nameArray et al. in turn - mainArray holds values not variables.
HTH and you now understand why your code could never do what you intended - that is set the values stored in the variables nameArray et al..
I have a very strange behaviour with NSArray.
Firstly, i parsed an XML file and return a valid NSArray with NSDictionary inside:
NSArray *arrayOfDict = [parser parseWithXMLFile:filename];
In debugger it's fully valid. Then, i want to proccess all dictionaries in this array:
NSMutableArray* arrayOfProducts = [[NSMutableArray alloc] init];
for (NSDictionary* dict in arrayOfDict) {
Product* product = [[Product alloc] initWithName:dict[#"name"]
type:dict[#"type"]
imageName:dict[#"image"]
description:dict[#"description"]];
[arrayOfProducts addObject:product];
[product release];
}
And in this loop is a problem: a dict variable has value nil. And i don't know what to do with this. In debugger i evaluate value of arrayOfDict[someIndex] and get a right value, but in the programm itself it doesn't work.
May be it's the problem with MRR, i don't feel myself confidenly while using MRR and there is a mistake of using it.
P.S. I know that using MRR is stupid today, but in this project i must use it.
I am using below function to check whether if an object in an array is present in another array. If the object not present, then I will ADD that object to the new array, or else that object will NOT be included in the new array that I instantiated.
+ (NSMutableArray *)loadUngroupedSpeakerList
{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *speakerList = [[NSMutableArray alloc] init];
NSArray *speakerIDList = [userDefaults objectForKey:DATA_SPEAKER_IDLIST];
NSArray *speakerIDListGrouped = [userDefaults objectForKey:DATA_SPEAKER_IDLIST_GROUPED];
//**** this is for checking the contents of speakerIDListGrouped ****//
for(NSString *speakerID in speakerIDListGrouped)
{
NSLog(#"FLOWCHECK~ loadUngroupedSpeakerList check content:%#", speakerID);
}
for(NSString *speakerID in speakerIDList)
{
if(![speakerIDListGrouped containsObject:speakerID])
{
NSLog(#"FLOWCHECK~ loadUngroupedSpeakerList: speakerID: %#", speakerID);
NSDictionary *speakerDict = [userDefaults dictionaryForKey:[NSString stringWithFormat:#"%#%#", DATA_SPEAKER_DICT, speakerID]];
[speakerList addObject:speakerDict];
}
}
return speakerList;
}
In the above code, speakerList contains all the speakerIDs. While speakerIDListGrouped only contains the speakerIDs that are used in a group. My function needs to eliminate all the speakerIDs used in a group so I did it in a way just like the above code.
My Problem:
When I run the code, I notice that even if speakerIDListGrouped contains the object in speakerIDList, these two lines would still be executed
NSDictionary *speakerDict = [userDefaults dictionaryForKey:[NSString stringWithFormat:#"%#%#", DATA_SPEAKER_DICT, speakerID]];
[speakerList addObject:speakerDict];
Whereas to I understand, It should not happen. Because I only allowed them to be executed only if speakerIDList does not contain that object.
This is the log when I execute the code:
2015-06-15 19:31:24.849 soulbeats[1936:433953] FLOWCHECK~ loadUngroupedSpeakerList check content:72243140485836704
2015-06-15 19:31:24.850 soulbeats[1936:433953] FLOWCHECK~ loadUngroupedSpeakerList check content:7782687177520836128
2015-06-15 19:31:24.850 soulbeats[1936:433953] FLOWCHECK~ loadUngroupedSpeakerList: speakerID: 72243140485836704
2015-06-15 19:31:24.851 soulbeats[1936:433953] FLOWCHECK~ loadUngroupedSpeakerList: speakerID: 7782687177520836128
As can be seen, speakerIDListGrouped DOES contain the two objects. However, when I tried replacing the string inside the lower for loop by hardcoding it to one of the objects I printed on Log, which was 72243140485836704. The function now works properly, I mean it didn't execute the two lines I showed before.
I am now confused. What is the difference between the string I hardcoded and the one that was obtained from the array? The contents are the same.
Many Thanks!
I did the same thing it is working fine...
NSMutableArray *speakerList = [[NSMutableArray alloc] init];
NSArray *speakerIDList = #[#"a",#"b",#"c",#"d",#"e"];
NSArray *speakerIDListGrouped =#[#"a",#"b",#"f",#"g",#"h"];
for(NSString *speakerID in speakerIDListGrouped)
{
NSLog(#"%#", speakerID);
}
for(NSString *speakerID in speakerIDList)
{
if(![speakerIDListGrouped containsObject:speakerID])
{
NSLog(#"FLOWCHECK~ loadUngroupedSpeakerList: speakerID: %#", speakerID);
[speakerList addObject:speakerID];
}
}
There might be some issue with the objects inside the array....
This answer will help other's. It's very simple, use following method of NSArray
id commonObject = [array1 firstObjectCommonWithArray:array2];
Ref: https://developer.apple.com/documentation/foundation/nsarray/1408825-firstobjectcommonwitharray?language=objc
I have an array which contains multiple Dictionaries each one with 3 keys (#"date", #"username", #"text").
What I want to check for, is whether the same user (#"username") exists in more than one dictionary in that Array. And, if she does, combine the text for those "duplicates" into one dictionary.
I have considered this answer to check for duplicates and this one
but I cannot figure out how to combine these two.
Jumping in here because although I think you should work on the code yourself first, I think Miro's answer is more complicated than the issue requires and though I like the idea of using predicates in Greg's answer, here's a 3rd solution that (1) wouldn't require you to change your data structure and (2) references the necessary loops...
The way I'd do it: Create an NSMutableArray then start adding the usernames in order. If the NSMutableArray already contains the username though, don't add another instance of the username, but instead merge the dictionary info.
ex.
// Note: I'm calling your array of user dictionaries userArray.
// Create a username array to store the usernames and check for duplicates
NSMutableArray *usernames = [[NSMutableArray alloc] init];
// Create a new userArray to store the updated dictionary info, merged
// entries et. al.
NSMutableArray *newUserArray = [[NSMutableArray alloc] init];
// Go through the array of user dictionaries
for (NSDictionary *userDict in userArray) {
// If the usernames array doesn't already contain the username,
// add it to both the usernames array and the newUserArray as is
if (![usernames containsObject:[userDict objectForKey:#"username"]]) {
[usernames addObject:[userDict objectForKey:#"username"]];
[newUserArray addObject:userDict];
}
// Otherwise, merge the userArray entries
else {
// Get a mutable copy of the dictionary entry at the first instance
// with this username
int indexOfFirstInstance = [usernames indexOfObject:[userDict objectForKey:#"username"]];
NSMutableDictionary *entry = [[newUserArray objectAtIndex:indexOfFirstInstance] mutableCopy];
// Then combine the "text" or whatever other values you wanted to combine
// by replacing the "text" value with the combined text.
// (I've done so with a comma, but you could also store the value in an array)
[entry setValue:[[entry objectForKey:#"text"] stringByAppendingString:[NSString stringWithFormat:#", %#", [userDict objectForKey:#"text"]]] forKey:#"text"];
// Then replace this newly merged dictionary with the one at the
// first instance
[newUserArray replaceObjectAtIndex:indexOfFirstInstance withObject:entry];
}
}
Maybe something like this [untested] example? Loop through, maintain a hash of existing items, and if a duplicate is found then combine with existing and remove.
NSMutableArray main; // this should exist, with content
NSMutableDictionary *hash = [[NSMutableDictionary alloc] init];
// loop through, backwards, as we're attempting to modify array in place (risky)
for(int i = [main count] - 1; i >= 0; i--){
// check for existing
if(hash[main[i][#"username"]] != nil){
int existingIdx = [hash[main[i][#"username"]] integerValue]; // get existing location
main[existingIdx][#"text"] = [main[existingIdx][#"text"] stringByAppendingString:main[i][#"text"]]; // "combine text" .. or however you'd like to
[main removeObjectAtIndex:i]; // remove duplicate
} else {
[hash setValue:[[NSNumber alloc] initWithInt:i] forKey:main[i][#"username"]]; // mark existance, with location
}
}
If you use NSMutableDictionary, NSMutableArray and NSMutableString you can do it with predicate like that:
NSMutableDictionary *d1 = [#{#"username": #"Greg", #"text" : [#"text 1" mutableCopy]} mutableCopy];
NSMutableDictionary *d2 = [#{#"username": #"Greg", #"text" : [#"text 2" mutableCopy]} mutableCopy];
NSMutableDictionary *d3 = [#{#"username": #"John", #"text" : [#"text 3" mutableCopy]} mutableCopy];
NSMutableArray *array = [#[d1, d2, d3] mutableCopy];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"username = %#", #"Greg"];
NSArray *filterArray = [array filteredArrayUsingPredicate:predicate];
NSMutableDictionary * firstDict = filterArray[0];
for (NSDictionary *d in filterArray)
{
if (firstDict != d)
{
[firstDict[#"text"] appendString:d[#"text"]];
[array removeObject:d];
}
}