Editing a local instance of a object changes the object in its main array - objective-c

In my app I created a bunch of object classes for data that I save in NSUserDefaults.
I get the item by:
LauncherToDoItem *item = [[ActionHelper sharedInstance] actionList][indexPath.row];
then i pass it on to a editing view controller:
LauncherEditActionViewController *editActions = [[LauncherEditActionViewController alloc] initWithToDoItem:item];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:editActions];
[self presentViewController:navController animated:YES completion:nil];
In the view controller I have a table that shows data from a editing version of the item.
- (id)initWithToDoItem:(LauncherToDoItem *)toDoItem {
self=[super initWithStyle:UITableViewStyleGrouped];
if (self) {
item = toDoItem;
editedToDoItem = toDoItem;
}
return self;
}
When I edit the editedToDoItem it also writes the the item, so I assume it's also writing to the version in the array? Why is it that by editing 1 of them affect it all? I dont save it back to the array yet but values save automatically.

That's because both item and editedToDoItem point to the same object — and the same exact location in memory. When you write item = toDoItem, what you're really doing is saving the pointer to toDoItem inside the variable item. It's a reference to the original object, not a copy, because Objective-C object variables are pointers to the objects in memory.
Consider the following code:
NSMutableString *string = [#"Hello, world!" mutableCopy];
NSMutableString *a = string;
NSMutableString *b = string;
// string, a, and b point to the same exact object.
[string appendString:#" Hi again!"];
NSLog(#"%#", a); // => "Hello, world! Hi again!"
[b appendString:#" Whoa!"];
NSLog(#"%#", a); // => "Hello, world! Hi again! Whoa!"
When you fetch the item from your array, then store it twice in the editing controller, you're just passing around references to the same object, which, when edited, will reflect on all other references that you have — because they point to the same location in memory.
If you really want several different copies of the object (so that editing one doesn't affect the others), you have to actually copy the objects by making them conform to the NSCopying Protocol and using [item copy].

Related

Objective-c PickerView displays memory locations instead of object variable names from singleton

Overview: I've got a MapKit map, a PickerView, and a static array of weapon objects (Each weapon having some member variable data). The intent is to use the picker view and display some information on my map (range, name).
Need some help with the array of objects stored in my Singleton. I'm passing the array successfully to the map but the picker is displaying the memory locations of the objects instead of the name.
SingletonFile.m where I create the array of objects
static dispatch_once_t pred;
static SingletonFile *shared = nil;
dispatch_once(&pred, ^{
shared = [[SingletonFile alloc] init];
shared.theWeapons = [NSMutableArray arrayWithArray:
#[[[weapon alloc] initWithName:#"M16" weaponPicName:#"M16 Pic Name"],
[[weapon alloc] initWithName:#"M20" weaponPicName:#"M20 Pic Name"],
[[weapon alloc] initWithName:#"M3" weaponPicName:#"MyName"]
]];
//This is my attempt to create a second array with only the weaponPicName's from theWeapons array created above.
shared.theWeaponNameArray = [shared.theWeapons valueForKey:#"weaponPicName"];
//This line produces the error, not key value coding-compliant for the key weaponName
//weaponName and weaponPicName are a part of the weapon object
});
return shared;
Weapon.m constructor for the weapon object
-(id)initWithName:(NSString*)weaponName weaponPicName:(NSString*)weaponPicName {
self = [super init];
if (self) {
_weaponName = weaponName;
_weaponPicName = weaponPicName;
}
return self;
}
MapViewController.m is where I get theWeapons array from my singleton class
//Initialize tableNames to the singleton name array for use in picker
self.tableNames = [SingletonFile weaponSingleton].theWeapons;
MapViewController.m (further down) is my picker code.
- (IBAction) pickType:(id)sender {
arrayPickerRows = self.tableNames;
[ActionSheetStringPicker showPickerWithTitle:#"Select"
rows:arrayPickerRows
initialSelection:0
doneBlock:^(ActionSheetStringPicker *picker, NSInteger selectedIndex, id selectedValue) {
selectedRow = [arrayPickerRows objectAtIndex:selectedIndex];
[self typePickerDone:sender];
}
cancelBlock:^(ActionSheetStringPicker *picker) {
NSLog(#"Block Picker Canceled");
}
origin:sender];
}
- (void) typePickerDone:(UIButton*) sender {
[self.WeaponPickerbutton setTitle:selectedRow forState:UIControlStateNormal];
}
I'm sure the answer has to do with accessing the weaponName of the objects. I've tried to create an array of weaponNames from the array of objects using valueForKey but it was saying my keys were non value compliant.
(Also aware I can do all this with a .plist but I'm trying to learn Singletons and work with arrays of objects)
Your arrayPickerRows stores a array of the weapons. When you set the title for the button, so when you use [arrayPickerRows objectAtIndex:selectedIndex], you will get the address of a weapon at index selectedIndex.
In your code, you don't have the definition for the variable selectedRow. However, it should be of class weapon. When setting the name of the button, you should use the following code:
[self.WeaponPickerbutton setTitle:selectedRow.weaponName forState:UIControlStateNormal];

addObject replaces all the previous objects in NSMutableArray

In my IOS app, I have two view controllers. ControllerA and ControllerB. I have an array of objects in ControllerA. ControllerB creates a new Object and adds it to the array in ControllerA. I do this with a reference to the array and pass that to ControllerB. I add a new object in ControllerB, but when I move back to ControllerA, the array has the right size, but all of the contents are the same.
The array is of type Group.
What do you think the issue is?
Code that adds object in controllerB:
NSString *groupName = userName.text; //gets value from UITextField
NSString *pword = password.text;
Group *newgroup = [[Group alloc]init:groupName :pword]; //init values are just NSString's in a class called Group
[groups addObject:newgroup];
[ViewController print];
[self dismissModalViewControllerAnimated:YES];

Issue with NSMutableArray visibility / retain

Alright so I am a little new to the NSMutableArray class and I think I am missing something obvious. I have an object pass a NSMutable Array to my window controller like so in my.m:
summaryWindow = [[SummaryWindowController alloc] init];
[summaryWindow setGlobalStatusArray:globalStatusArray];
I have the receiver method in the summaryWindow object as so:
-(void)setGlobalStatusArray:(NSMutableArray *)myArray
{
if ([myArray count] >0) {
if (globalStatusArray) {
[globalStatusArray release];
}
globalStatusArray = [[NSMutableArray alloc] initWithArray:myArray];
NSLog(#"Summary Window Init with new array: %#",globalStatusArray);
I see the NSLog no problem, and in that same object (summaryWindow) I have the following method:
- (NSMutableArray *)getGlobalStatusArray
{
return globalStatusArray;
}
Now I have globalStatusArray declared in my .h file as
NSMutableArray *globalStatusArray;
So shouldn't This be retained because I am using: initWithArray?
When I try to access this value in an another IBAction method:
- (IBAction)refreshButtonClicked:(id)sender
{
NSLog(#"The user has clicked the update button");
[ aBuffer addObjectsFromArray: globalStatusArray];
NSLog(#"Buffer is currently:%#",aBuffer);
[tableView reloadData];
}
The NSMutable array is null
2011-08-18 10:40:35.599 App Name[65677:1307] The user has clicked the update button
2011-08-18 10:40:35.600 App Name[65677:1307] Buffer is currently:(
)
I have tried using my own method to get the value i.e. [ self getGlobalStatusArray] to but I am missing something huge. FYI aBuffer is also declared in my .h ,
As albertamg noted, that looks like an empty array rather than nil, and a released object doesn't magically become nil under normal circumstances anyway.
This smells strongly of two different objects. Try logging self in your methods and see if one instance is getting the array and another is interacting with the UI.
This code isn't doing anything useful:
if ([myArray count] >0) {
if (globalStatusArray) {
[globalStatusArray release];
}
globalStatusArray = [[NSMutableArray alloc] initWithArray:myArray];
If the count of the old array is zero, it's leaking the actual array object. If the count is not zero, then it's releasing it properly. Just do the release and don't bother counting.
Are you sure there's actually something in myArray?
joe

Editing one object in an NSMutableArray also changes another object in the NSMutableArray

I had a navigation application that was working normally. In the table view, the last item is called "add item", and if the user pressed it, it would create a new object and pass it to another view where the user could enter the details for that object. When the user returned to the previous screen, the new object would show in the array which was displayed in the table.
I changed it so that the "add item" field is always the first field in the table, not the last. I made the appropriate changes so that the array would display correctly on the table. However, now I am noticing strange behavior.
If I edit the first object in the array, the 7th object also changes to be the same as this object. If I edit the second object in the array, the fourth and sixth object also change to be the same. If I edit the third item in the array, the fifth object changes to be the same.
What could be happening?
In the viewDidLoad: method I initialize the object like this:
PersonDetails *personDetails = [[PersonDetails alloc] init];
This is the method that gets executed when a user selects a row on the table
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// Navigation logic may go here. Create and push another view controller.
updatePersonArray = YES;
arrayIndex = indexPath.row-1;
editPerson = [[EditClassController alloc] initWithNibName:#"EditPerson" bundle:nil];
editPerson.title = #"Edit Person";
if (arrayIndex != -1) {
personDetails = [classArray objectAtIndex:arrayIndex];
}
else {
personDetails = [[PersonDetails alloc] init];
}
editPerson.personDetails = personDetails;
[self.navigationController pushViewController:editPerson animated:YES];
[editPerson release];
}
This is what the viewWillAppear looks like. It will update the table after an object has been edited.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if ([personDetails isEmpty]) {
updatePersonArray = NO;
}
if (updatePersonArray) {
if (arrayIndex == -1) {
NSLog(#"adding new object to array");
[personArray addObject:personDetails];
}
else {
NSLog(#"replacing object at index %d", arrayIndex);
[personArray replaceObjectAtIndex:arrayIndex withObject:personDetails];
}
[self saveArrayToDisk];
[self.tableView reloadData];
updatePersonArray = NO;
}
else {
//load the array from disk
NSLog(#"loading array from disk");
NSData *theData = [[NSUserDefaults standardUserDefaults] objectForKey:#"personArray"];
if (theData != nil) {
NSLog(#"found something");
personArray = [[NSMutableArray alloc] initWithArray:[NSKeyedUnarchiver unarchiveObjectWithData:theData]];
}
else {
personArray = [[NSMutableArray alloc] init];
}
}
}
Edit: I solve the problem by implementing NSCopy for the person object and then making a copy of the object from the array instead of directly pointing to the object in the array. Anyone know why this solved the problem?
Edit: I solve the problem by
implementing NSCopy for the person
object and then making a copy of the
object from the array instead of
directly pointing to the object in the
array. Anyone know why this solved the
problem?
The original problem was pretty much guaranteed to be an issue of having the same PersonDetails in the array multiple times. If you were to do something like:
for (id p in myArray) NSLog("%p", p);
I'd bet that some of the addresses would be the same, indicating same object in array multiple times.
Which is why copying the object "fixed" the problem. You are hiding the dodgy logic that led to the above situation by making a copy on every insertion.
this part here looks a bit dodgy with regard to memory ownership:
if (arrayIndex != -1) {
// here you get back an autorelease object - which you haven't retained
personDetails = [classArray objectAtIndex:arrayIndex];
}
else {
// here you create an object with retainCount =1
personDetails = [[PersonDetails alloc] init];
}
// depending on your property attribute this may or may not work as you expect
editPerson.personDetails = personDetails;
i.e. #property(??) personDetails

Issues declaring already existing NSMutableArray in new class

I have a class (DataImporter) which has the code to download an RSS feed. I also have a view and separate class (TableView) which displays the data in a UITableView and starts the parsing process, storing parsed information in an NSMutableArray (items) which is located in the (TableView) subclass.
Now I wish to add a UIMapView which displays the items in the (items) NSMutableArray. Herein lies the issue - I need to somehow get the data from the (items) NSMutableArray into the new (mapView) subclass which I'm struggling with - and I preferably don't want to have to create a new class to download the data again for the mapView class when it already is in the applications memory. Is there a way I can transfer the information from the NSMutableArray (items) class to the (mapView) class (i.e. how do I declare the NSMutableArray in the (mapView) class)?
Here's a overview of how the system works:
App opened> Data downloaded (using DataImporter class) when (TableView) viewDidLoad runs> Data stored in NSMutableArray accessible by the (TableView) class> And from here I need to access and declare the array from a new (mapView) class.
Any help greatly appreciated, thanks.
Code for viewDidLoad MapKit:
Data *data = nil;
NSString *ilocation = [data locations];
NSString *ilocation2 = #"New Zealand";
NSString *inewlString;
inewlString = [ilocation stringByAppendingString:ilocation2];
NSLog(#"inewlString=%#",inewlString);
if(forwardGeocoder == nil)
{
forwardGeocoder = [[BSForwardGeocoder alloc] initWithDelegate:self];
}
// Forward geocode!
[forwardGeocoder findLocation: inewlString];
Code for parsing data into original NSMutable Array:
- (void)beginParsing {
NSLog(#"Parsing has begun");
//self.navigationItem.rightBarButtonItem.enabled = NO;
// Allocate the array for song storage, or empty the results of previous parses
if (incidents == nil) {
NSLog(#"Grabbing array");
self.datas = [NSMutableArray array];
} else {
[datas removeAllObjects];
[self.tableView reloadData];
}
// Create the parser, set its delegate, and start it.
self.parser = [[DataImporter alloc] init];
parser.delegate = self;
[parser start];
}
Since you have a property self.datas you can get a reference to the data just by calling that from your UIMapView, or by assigning to some target property in your map view from whatever object controls both views:
mapView.someArrayProperty = tableView.datas
However, you might want to rethink your overall program structure. Keeping what is in effect model data inside views is not good practice and tends make things pretty spaghetti-like as you add more objects and functionality.