Say I have NSMutableArray *array1 with 10 objects. I want to create an *array2 and add 5 objects from array1 to array2, and I want it so that when I change these object properties from array2, they also change the 5 specific objects from array1 as well. How would I do this?
Edit: Ok I think I asked the wrong question. It's more about passing by reference and pointers, which I confuse too much:
NSMutableArray *mainArray;
NSMutableArray *secondaryArray;
NSMutableDictionary *dic1;
[mainArray addObject:dic1];
[self changeValues:[mainArray lastObject]];
-(void)changeValues:(NSMutableDictionary*)someDic
{
[secondaryArray addObject:someDic];
NSMutableDictionary *aDic=[secondaryArray lastObject];
...//some code to change values of aDic
//by changing aDic, I want to also change the same dic from mainArray
//so [mainArray lastObject] should be the same exact thing as [secondaryArray lastObject]
}
How would I change the above code so the changes reflect in both arrays?
NSMutableArray *array2 = [NSMutableArray array];
for (int i=0; i<5; ++i){
[array2 addObject: [array1 objectAtIndex:i] ]
}
In this example you have the set of objects pointed by items of array1 as well as by items
of array2, since NSMutableArray contains pointers to objects, not objects theirselves.
Therefore, changing the object thru pointer in one array you may observe that change thru
pointer from other array.
Edit
#mohabitar, you already receive an answers. dic1, someDic and aDic - all these values are same. Just change aDic(or someDic) and see result.
This sounds like a good case for some KVC (Key-Value Coding).
With KVC you can create indexed properties and have the KVC engine create an array proxy for the indexed property, which will then allow you to operate on the indexed property as if it were an array.
Below is a quick proof-of-concept piece of code, tested on both OS X and iOS.
Interface:
#property (strong) NSMutableArray *mainArray;
Implementation:
#synthesize mainArray = _mainArray;
- (id)init
{
self = [super init];
if (self) {
// For simplicity, use strings as the example
_mainArray = [NSMutableArray arrayWithObjects:
#"1st element",
#"2nd element",
#"3rd element",
#"4th element",
#"5th element",
#"6th element",
#"7th element",
#"8th element",
#"9th element",
#"10th element",
nil];
}
return self;
}
// KVC for a synthetic array, accessible as property #"secondaryArray"
- (NSUInteger) countOfSecondaryArray
{
return 5;
}
- (id) objectInSecondaryArrayAtIndex: (NSUInteger) index
{
// In practice you would need your mapping code here. For now
// we just map through a plain C array:
static NSUInteger mainToSecondaryMap[5] = {1,4,5,7,8};
return [self.mainArray objectAtIndex:mainToSecondaryMap[index]];
}
- (void) watchItWork
{
NSArray *secondaryArray = [self valueForKey:#"secondaryArray"];
// See how the sub array contains the elements from the main array:
NSLog(#"%#", secondaryArray);
// Now change the main array and watch the change reflect in the sub array:
[self.mainArray replaceObjectAtIndex:4 withObject:#"New String"];
NSLog(#"%#", secondaryArray);
}
There is more information in the docs, specifically the part on Indexed Accessor Pattern.
Related
I am trying to develop a function to add Dictionaries into Arrays.
This function looks this way but doesn't work:
in the implementation of the CBLModel I have the property emailList. The property I want to be an NSArray of NSDictionary's.
-(void) addEmail: (NSDictionary*) c{
NSMutableArray* mutArrayEmail= [NSMutableArray arrayWithObject: self.emailList];
[mutArrayEmail addObject:c];
self.emailList = [NSArray arrayWithObject:mutArrayEmail];
}
And the implementation would be something like:
[persInf addEmail: email1];
[persInf addEmail: email2];
[persInf addEmail: email3];
Any help?
EDIT
Thanks to everyone for your reply.
I have inserted in the main code the following (in order to not test everything easier):
NSDictionary *inventory = #{
#"Mercedes-Benz SLK250" : [NSNumber numberWithInt:13],
#"Mercedes-Benz E350" : [NSNumber numberWithInt:22],
#"BMW M3 Coupe" : [NSNumber numberWithInt:19],
#"BMW X6" : [NSNumber numberWithInt:16],
};
NSMutableArray *nsmut= [[NSMutableArray alloc] init];
[nsmut addObject: inventory];
[nsmut addObject: inventory];
[nsmut addObject: inventory];
NSArray *nsarray= [NSArray arrayWithArray:nsmut];
[persInf setEmailList:nsarray];
After this I get an error. I post this code sample as may be easier to see where the error can be
You only want to create the array if it doesn't already exist:
- (void)addEmail:(NSDictionary *) c{
if (!self.emailList) {
self.emailList = [[NSMutableArray alloc] init];
}
[self.emailList addObject:c];
}
This is lazy initialization as the array is created on first use. This is fine, as long as you realise that the array can be nil at times in the object's lifetime (which is less of a problem in Objective-C than in other languages).
A more conventional approach is to override an init method and allocate the array there.
Declare your NSMutableArray outside of your addEmail: method. You can use addObject: as-is.
Since emailList is a property you should override the getter to lazily instantiate it when its needed, like so.
- (NSMutableArray *)emailList {
if(!_emailList) {
_emailList = [NSMutableArray array];
}
return _emailList
}
To be thorough you could write that code using the ternary and make it a bit shorter:
- (NSMutableArray *)emailList {
return _emailList = _emailList ?: [NSMutableArray array];
}
Finally once you've overrode the getter your addEmail method becomes:
- (void)addEmail: (NSDictionary*)c {
[self.emailList addObject:c];
}
If you have to store emailList as an NSArray, I guess you need to set it with
self.emailList = [NSArray arrayWithArray:mutArrayEmail];
I'm far from an expert, though, so there may be problems with this approach. I'd welcome comments to that effect!
You can declare additional property of NSMutableArray (it can be in your class extension):
#property(nonatomic, copy)NSMutableArray *tempArray;
Initialize it in the init
_tempArray = [[NSMutableArray alloc] init];
And change your setter method to:
-(void) addEmail: (NSDictionary*) c{
[self.tempArray addObject:c];
}
And change your getter to:
- (NSArray *)emailList {
return [NSArray arrayWithArray:[self.tempArray copy]];
}
I see you are new to SO, so a little (friendly!) advice: you need to provide more detail in your questions of exactly what you've tried and what has gone wrong; e.g. "After this I get an error." does not help others help you.
Now reading your question and comments two things pop out: assertions about CBLModel, and how to add items to an array.
A. First, CBLModel. You've stated a few times you need an NSArray as:
I can't declare it as Mutable array or it won't work in the CBLModel
What do you mean by this? If you are saying that the methods in CBLModel take NSArray type arguments then you can pass NSMutableArray's as an NSMutableArray is an NSArray (i.e. it is a subclass) - this is one of the central pillars of inheritance-based languages like Objective-C.
If instead you mean that CBLModel does not function correctly if you pass it an NSMutableArray, say because you yourself mutate the array after passing it and that effects CBLModel in an invalid way[*], then you can simply create an immutable copy ([NSArray arrayWithArray:someMutableArray]) or just another mutable copy ([someMutableArray copy]) which you pass only to CBLModel.
B. Adding items to an array. Let's look at your code, line by line, so you can see what it is doing:
NSMutableArray* mutArrayEmail= [NSMutableArray arrayWithObject: self.emailList];
This creates a new NSMutableArray initialised with a single element obtained from self.emailList, and stores a reference to it in mutArrayEmail. E.g. if before execution self.emailList references an array [1, 2, 3], then after execution mutArrayEmail references a (mutable) array [ [1, 2, 3] ] - a single element array whose element is itself an array.
[mutArrayEmail addObject:c];
You add the object referenced by c, so using the same example you now have [ [1, 2, 3], c ] - an array with two elements.
self.emailList = [NSArray arrayWithObject:mutArrayEmail];
And finally you create a new array with the single object referenced by mutArrayEmail. Keeping the same example you now have [ [ [1, 2, 3], c ] ]. That is probably not what you wanted, or what CBLModel accepts.
The class method arrayWithArray: creates an array with the same elements as another array. If you used that method the above example would produce [1, 2, 3, c], which is probably what you want.
HTH
[*] Any classes which accept objects of types with mutable subtypes [arrays, strings, etc.] would normally be written such that a mutation after the object is passed does not effect the behaviour in undesirable ways, i.e. the classes would copy objects if needed. This it is recommended that properties of such type are defined with the copy attribute.
It's a bad idea, generally, to expose a mutable array to the outside world if it's part of an object's state. It means the object's state can be amended without the object knowing about it.
Here's how I would do this:
Have a readonly property that lets people get the array.
Have a method to add objects to the array.
#interface MyClass : NSObject
#property (nonatomic, readonly, strong) NSArray* emailList;
-(void) addEmail: (NSDictionary*) newEmail;
#end
In the implementation, have an mutable instance variable to back the property, initialise it in your designated initialiser. Implement your add method.
// All code assumed to be using ARC
#implementation MyClass
{
NSMutableArray* _emailList;
}
#synthesize emailList = _emailList;
-(id) init
{
self = [super init];
if (self != nil)
{
_emailList = [[NSMutableArray alloc] init];
}
return self;
}
-(void) addEmail: (NSDictionary*) newEmail
{
[_emailList addObject: newEmail];
}
#end
Although, the actual object returned by the property is an NSMutableArray the rule is to program to the interface, which means that when an interface says it returns an immutable object, even if you know it's really a mutable subclass, you are not allowed to send it mutating methods.
If, however, you are paranoid, implement the getter for the property like this:
-(NSArray*) emailList
{
return [_emailList copy];
}
Due to an example from Apress,for a search criteria,it has a soultion:it will firstly get a mutable copy and search the string and then will remove objects that are not in range of that string.
in the Mutable DeepCopy it has function as follow:
#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];
}
return returnDict;
}
#end
when I don't understand any code I will debug it,so I debugged it and in the beginnings when it wants to show the whole array,in the if statement it will go to oneCopy=[oneValue mutableCopy]; I want to know that why it will choose this selector and not the MutableDeep selector? I can't understand this function totally.what is the main purpose of this function?
for searching it has this function
-(void)handleSearchForTerm:(NSString *)searchTerm
{
NSMutableArray * sectionsToRemove=[[NSMutableArray alloc]init];
[self resetSearch];
for(NSString * key in self.keys)
{
NSMutableArray * array=[names valueForKey:key];
NSMutableArray *toRemove=[[NSMutableArray alloc]init];
for(NSString * name in array)
{
if([name rangeOfString:searchTerm options:NSCaseInsensitiveSearch].location==NSNotFound)
[toRemove addObject:name];
}
if([array count]==[toRemove count])
[sectionsToRemove addObject:key];
[array removeObjectsInArray:toRemove];
}
[self.keys removeObjectsInArray:sectionsToRemove];
[table reloadData];
}
and for reset the search and recovering whole it has this function below.
my problem is that in self.keys we removed objects with help of handleSearchForTerm but when we go to resetsearch it will return again those whole keys without any removal maybe my main problem is that I can't understand Mutablecopy and DeepMutableCopy.
-(void)resetSearch{
NSMutableDictionary *allNamesCopy=[self.allNames mutableDeepCopy];
self.names=allNamesCopy;
NSMutableArray *keyArray=[[NSMutableArray alloc]init];
[keyArray addObjectsFromArray:[[self.allNames allKeys]sortedArrayUsingSelector:#selector(compare:)]];
self.keys=keyArray;
}
They both have some compare & contrast features...
First comparing: Both are mutable, you can alter the object itself.
Second contrasting/differences:
MutableCopy: Similar to Call-by-Reference.
MutableDeepCopy: Similar to Call-by-Value.
A copy of a container (e.g. NSArray) copies only the pointers to objects and sends them retain.
A mutableCopy does the same thing, but the resulting container can be modified, i.e. Pointers can be added or removed.
A deep copy would also make copies of the individual elements.
Take for example an NSArray of NSMutableString instances. You cannot modify the array itself, but you can alter each mutable string. Now if you copy the array and modify one of the strings then the copied array also has the changes because it's pointers point to the same instances as the first array.
If you deep copy the array and alter a mutable string, then the new array elements are not modified. In fact copy always makes an immutable copy.
i got this exception:
[__NSArrayI replaceObjectAtIndex:withObject:]: unrecognized selector sent to instance
when i try to replace a specific element by another:
EDIT:
this is my whole code:
//declaring an AppDelegate instance
AppDelegate *myAppDelegate=(AppDelegate *)[[UIApplication sharedApplication]delegate];
//get the array in which we have stored all the choosed themes
NSMutableArray *aMutableArray=myAppDelegate.themesChoosed;
for (int i=0; i<[aMutableArray count]; i++) {
NSString *str=[NSString stringWithString:[aMutableArray objectAtIndex:i]];
if ([str isEqualToString:#"B1"]) {
[aMutableArray replaceObjectAtIndex:i withObject:#"B2"];
}
}
I maked sure that the B1 element does exist in the array.
What is happening to your NSMuatbleArray before you get into the for loop?
Is it a property? If so, what is the property declaration? Did you use copy?
If you implement a property like this:
#property (nonatomic, copy) NSMutableArray *myArray;
...then you can run into problems like this because the synthesized setter sends copy to the array, which results in an immutable copy. If this is the case, you need to implement your own setter that calls mutableCopy on the array (or just use retain instead and design your code a little differently).
EDIT:
Based on your comments below and the updated code, I'm sure the problem must be something to do with the array on the app delegate not being mutable.
Try this:
NSMutableArray *mutableThemeseChoosed = [NSMutableArray arrayWithArray:myAppDelegate.themesChoosed];
I just tried your code and it works fine.
NSMutableArray *aMutableArray = [NSMutableArray arrayWithObjects:[NSString stringWithString:#"A1"],[NSString stringWithString:#"B1"],[NSString stringWithString:#"B2"],[NSString stringWithString:#"A1"],[NSString stringWithString:#"A2"],[NSString stringWithString:#"A1"],[NSString stringWithString:#"A1"], nil];
NSLog(#"%#",aMutableArray);
for (int i=0; i<[aMutableArray count]; i++) {
NSString *str=[NSString stringWithString:[aMutableArray objectAtIndex:i]];
if ([str isEqualToString:#"B1"]) {
[aMutableArray replaceObjectAtIndex:i withObject:#"B2"];
}
}
NSLog(#"%#",aMutableArray);
Your problem is - as the error tells - that your mutable array is a NSArray (which is not mutable)
What you get out of myAppDelegate.themesChoosed; is likely a NSArray. Try the following: NSMutableArray *aMutableArray= [NSMutableArray arrayWithArray:myAppDelegate.themesChoosed];
I am currently trying to write a class to create faux grid system to keep track of a NSMutableArray of game entities using a NSMutableArray of NSMutableArrays. Given my limited experience with Objective-C programming, I am unsure of how certain things work.
Here is the init method:
#define MAX_BALL_ROWCOUNT 6
#define MAX_BALL_COLCOUNT 4
- (id) initWithMutableArray:(NSMutableArray *)aList {
self = [super init];
if (self != nil) {
ballList = [[NSMutableArray alloc] initWithCapacity: MAX_BALL_ROWCOUNT];
for (int i=0; i<MAX_BALL_ROWCOUNT; i++) {
NSMutableArray *balls = [[NSMutableArray alloc] initWithCapacity:MAX_BALL_COLCOUNT];
[ballList addObject:balls];
[balls release];
}
int x = 0;
for (NSMutableArray *array in ballList) {
for (int i = 0; i<MAX_BALL_COLCOUNT; i++) {
[array addObject:[aList objectAtIndex:x]];
x++;
}
}
}
return self;
}
ballList is the class's NSMutableArray that will store NSMutableArrays.
aList is the NSMutableArray containing the GameEntities I wish to keep track of that is passed into this class.
All the sizes and amount of entities to store are fixed, which is why there is no checks on the sizes of the arrays nor the number of entities to store.
So the first question I have involves freeing memory. This is the dealloc function I currently have:
- (void) dealloc {
[ballList release];
[super dealloc];
}
Does calling a release on ballList cause the release to be called on the NSMutableArrays that it contains (which will subsequently call the release on the objects those NSMutableArrays contain) or do I have to write something like:
for (NSMutableArray *array in ballList) {
[array release];
}
[ballList release];
My second question involves the usage of this array of arrays. Is this the proper way to traverse through ballList?
- (void) update {
for (NSMutableArray *array in ballList) {
for (GameEntity *balls in array) {
(CGPoint) location = [balls getLocation];
[balls setLocation: CGPointMake(location.x+1, location.y+1)];
}
}
}
Lastly, in the code above where it sets the balls location, does it only affect the contents within ballList or does the original aList that is passed into ballList change as well? If the contents in the original aList do not change, how would I write it so that they do?
If people have suggestions for a better way to keep track of the entities in a grid system, I'd be open to those too. Thanks in advance.
First : One release is enough for the NSMutableArray instance to release all it's object.
[ballList release];
Second : Your code for updating GameEntity instance is fine and will also effect to the original aList (which you called) .
When the dealloc of a NSArray or NSMutableArray is called, all its contents gets a release message. So when you release ballList, if there there is no other owner (I guess in this case there is none) then its dealloc is called and you don't need to release the other arrays here.
Your loop traversal is fine. Though for 2D arrays instead of NSArray of NSArray I personally prefer pure C 2D array, at least in most of the cases.
When you are adding object in this way you are adding a reference in the array. So any change via the array's reference will be reflected in all references of the object. If you don't want that then add a copy of the object in the array.
Here's my code of generating data
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[array initWithCapacity:20];
}
- (IBAction) readlog:(id)sender {
for (int i = 0; i < 20; i++) {
NSDictionary *d = [NSDictionary dictionaryWithContentsOfFile:[path stringByAppendingFormat:#"/%d.log",i]];
[array addObject:d];
}
}
- (IBAction) writelog:(id)sender {
for (int i = 0; i < 20; i++) {
NSMutableDictionary *d = [NSMutableDictionary dictionary];
NSString *name = [NSString stringWithFormat:#"testfile%d", i];
[d setObject:[NSDate date] forKey:#"date"];
[d setObject:[path stringByAppendingFormat:#"/%d.log", i] forKey:#"path"];
[d setObject:name forKey:#"name"];
[d writeToFile:[path stringByAppendingFormat:#"/%d.log", i] atomically:YES];
}
and I bind my tableview column with appdelegate.array with keypath name/path/date
but it doesn't show any data in the array.. is there anything wrong here?
Thanks!
You haven't created an array.
init methods, including NSMutableArray's initWithCapacity:, initialize an existing (freshly-created) instance. You haven't created one, so you're sending that initWithCapacity: message to nil, which means it has no effect.
You need to create the array, then initialize it, then assign it to your array variable, preferably all in the same line.
There's also the issue that your table view will have already asked for the array by the time you receive the applicationDidFinishLaunching: message. You don't have one yet, so it gets nothing; by the time you create one, it has already asked you for it and gotten its answer, and does not know that it should ask again.
Create your array in init or initWithCoder: (I believe you will need the latter if your app delegate is in a nib), and implement and use Key-Value-Coding-compatible accessor methods to fill the array with values. When you send yourself accessor messages, you'll cause KVO notifications that will tip off the table view that it needs to ask for the array again. Assigning directly to the instance variable will not cause this effect.
A couple of other thingsā¦
You have three [path stringByAppendingFormat:#"/%d.log", i] expressions in two different methods. Don't repeat yourself. Move that to a method named something like logFileNameWithLogFileNumber: and send yourself that message to generate the filename. This will make the code both clearer and easier to maintain.
Finally, as a matter of style, you should not use stringByAppendingFormat: or stringWithFormat: to construct paths. Use stringByAppendingPathComponent: (in this case, together with stringWithFormat: to generate the filename). Clarity and pathname-separator-independence are virtues.