modifying copy of array object affect object stored in the array - ios

I have object copying issue.
#property (nonatomic,strong)ITEM *editingItem;
self.editingItem= nil;
self.editingItem = [[self.ItemsArray objectAtIndex:indexPath.row-1] copy];
self.editingrowIndex = indexPath.row;
When I assign some object to editingItem in didselect table row and start editing it in textfield delegates when i change the object property values then it is changning in the object stored in an array.but i want only editingItem object should have new values.But without updating array new values are getting stored from editingItem to object in the array.

The implementation provided by the OP, in the other answer is incorrect, as it does not copy the instance variables; rather it creates another reference to them. This would explain the issues the OP is seeing in his question (where that code should have been presented, and not in a separate answer).
Here is a better implementation:
- (id)copyWithZone:(NSZone *)zone
{
Item *copy = [[Item allocWithZone:zone] init];
copy->_nombre: [_nombre copy];
copy->_linea = [_linea] copy];
copy->_tags = [_tags copy];
copy->_htmlSource = [_htmlSource copy];
return copy;
}
Note that the instance variable copy statements provided in the other implementation is correct (aside from the missing copy call), but it won't work for private instance variables (a common occurrence), so I always stick to the form copy->_instanceVariable = [_instanceVariable copy];.
Also note that if the object derives from something other than NSObject then first statement should be:
Item *copy = [super copyWithZone:zone];
instead of:
Item *copy = [[Item allocWithZone:zone] init];
Further notes:
I find it highly suspect that you are keeping NSMutableString objects as instance variables. That is fairly unusual in my experience, having only used them as temporary objects for building immutable strings. I suspect you are not using the correct data type to hold this data.

#import <Foundation/Foundation.h>
#interface Item : NSObject <NSCopying>
#property (strong, nonatomic) NSString *nombre;//nombre del medicamento
#property (strong, nonatomic) NSString *linea;//linea a la que pertenece
#property (strong, nonatomic) NSMutableString *tags;//palabras por las que se puede encontrar en el buscador
#property (strong, nonatomic) NSString *htmlSource;//código html para mostrar su contenido
#property (strong, nonatomic) NSMutableString *obj;
-(id) copyWithZone: (NSZone *) zone;
#end
#implementation TempObject
-(id) copyWithZone: (NSZone *) zone
{
Item *copy = [[Item allocWithZone: zone] init];
[copy setNombre: self.nombre];
[copy setLinea: self.linea];
[copy setTags: self.tags];
[copy setHtmlSource: self.htmlSource];
return copy;
}
Override CopywithZone method in your item class and set each and every property inside that method.It is the solution for copying object.When you do allocwithzone and init,the new object is created with new address and old values which you have to set manually in copywithzone: method.So duplicate instance with old values.It is perfect solution working for me.

Related

Why are class objects the property attribute of retain and not copy? [duplicate]

This question already has answers here:
Objective-C declared #property attributes (nonatomic, copy, strong, weak)
(4 answers)
Closed 6 years ago.
I was trying to pass a custom object to the next view controller and I encountered this error -[ClassName copyWithZone:] unrecognized selector sent to instance
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"attemptDetails"])
{
ResultsVC *vc = segue.destinationViewController;
vc.selectedEntry = selectedEntry;
}
}
#property (nonatomic, retain) ClassName *selectedEntry; //Why is it retain and not copy?
I'm still very confused with property attributes and why certain types use certain attributes, like NSString uses (nonatomic, copy) and CLLocationCoordinate2D uses (nonatomic, readonly).
Could someone explain or link a reference to me how each property attribute works? Much thanks!
There are lots of descriptions for property attributes explanation,
Reference links,
Objective-C ARC: strong vs retain and weak vs assign
https://stackoverflow.com/a/4511004/4294543
#property and retain, assign, copy, nonatomic in Objective-C
Short & simple my understanding is like,
retain : It's working on the created object, and it just increase the reference count.
Here in your case you have already model class object so not need to copy in the second vc property,you just need to retain it to second vc property.
copy : The value you assigned to property can be copied & used for other purposes too(create shallow copy of object & need when object is mutable & need to release after finish with it).
nonatomic : Thread access is faster but you can't simultaneously access & change your property.
readonly : You can't directly assign the property new value.
Even i have run your case in the my project,
#import "ViewController.h"
#import "TestViewController.h"
#import "CustomClass.h"
#interface ViewController (){
CustomClass *classT;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
classT = [[CustomClass alloc]init];
classT.test = YES;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)btn:(id)sender {
TestViewController * vc = [self.storyboard instantiateViewControllerWithIdentifier:#"TestViewController"];
vc.className = classT;
[self presentViewController:vc animated:YES completion:nil];
}
#end
#import <UIKit/UIKit.h>
#import "CustomClass.h"
#interface TestViewController : UIViewController
#property (nonatomic,retain) CustomClass *className; // Work as i said
//#property (nonatomic,copy) CustomClass *className; // Makes a copy of an object, and returns it with retain count of 1. If you copy an object, you own the copy. This applies to any method that contains the word copy where “copy” refers to the object being returned thats why here you will get crash
#end
I have read couple of good article for memory management. According to rypress
Retain Attribute : The retain attribute is the Manual Retain Release version of strong, and it has the exact same effect: claiming ownership of assigned values. You shouldn’t use this in an Automatic Reference Counted environment.
Copy Attribute : The copy attribute is an alternative to strong. Instead of taking ownership of the existing object, it creates a copy of whatever you assign to the property, then takes ownership of that. Only objects that conform to the NSCopying protocol can use this attribute.
Even I went through some good link of stackoverflow as well. Joshua Nozzi's answer gave good explanation for retain vs copy.
Retain vs. Copy - Declared properties use retain by default (so you can simply omit it altogether) and will manage the object's reference count automatically whether another object is assigned to the property or it's set to nil; Use copy to automatically send the newly-assigned object a -copy message (which will create a copy of the passed object and assign that copy to the property instead - useful (even required) in some situations where the assigned object might be modified after being set as a property of some other object (which would mean that modification/mutation would apply to the property as well).
Also found good example here.
Code :
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:#"First",#"Second", nil];
NSMutableArray *copiedArray = [array mutableCopy];
NSMutableArray *retainedArray = [array retain];
[retainedArray addObject:#"Retained Third"];
[copiedArray addObject:#"Copied Third"];
NSLog(#"array = %#",array);
NSLog(#"Retained Array = %#",retainedArray);
NSLog(#"Copied Array = %#",copiedArray);
Output :
array = (
First,
Second,
"Retained Third"
)
2013-12-19 17:15:49.380 RetainVsCopy[2876:c07] Retained Array = (
First,
Second,
"Retained Third"
)
2013-12-19 17:15:49.381 RetainVsCopy[2876:c07] Copied Array = (
First,
Second,
"Copied Third"
)
See, both array and Retained Array are having same contents. This is because both are pointing to same memory/instance/object. Where as contents of Copied Array are different. This is because copy created a separate instance.
In Objective C you will find that each class actually has a structure behind it. The properties are shortcuts which create the value in structure, a getter and a setter. For instance:
#interface MyClass
#property id myValue;
#end
Will create:
#interface MyClass {
id _myValue;
}
#property id myValue;
#end
#implementation
- (id)myValue {
return _myValue;
}
- (void)setMyValue:(id)myValue {
_myValue = myValue;
}
#end
Now these flags such as retain and copy add additional logic to the setters and getters. Using copy will actually create a setter as:
- (void)setMyValue:(id)myValue {
_myValue = [myValue copy];
}
Which means that the value must have the copy method implemented. Since your object does not it crashes.
Why to use copy is for safety. This is rarely important for something as strings but it is important for something like an array. So for instance you create a property #property NSArray *myArray; which expects an un-mutable array but the problem is that you can set a mutable array as well: myClassInstance.myArray = [[NSMutableArray alloc] init];. Now 2 modules have the access to the same mutable array. So if the first object starts modifying the array while the other one expects the array to always be the same you may find some issues. For instance MyClass instance may use it as a data source for the table view and at some point the array is mutated but the cells are not added/removed and the table view will cause a crash.
To be honest you can simply leave all of these as default and modify them only when you really need to. The case like above is highly unlikely anyway.

Create an array that hold custom class object

I have class, that have several properties, it look like this:
#interface PlaceHolder : NSObject
#property (strong, nonatomic) NSString *name;
#property (strong, nonatomic) NSString *description;
#property (strong, nonatomic) NSString *webPage;
#property (strong, nonatomic) NSNumber *latitude;
What i need is, to create an array, that hold objects of that class. Obvious, properties will not be nil and will be different each time. So, that array must have several hundreds of PlaceHolder object, and it should be possible to get information for any of that object and it properties. But, when i try to create that array, in NSLog i see that it contain only (NULL) objects. This is how i try too add object to array:
In header i wrote:
#property (strong, nonatomic) PlaceHolder *place;
Then:
self.place = [[PlaceHolder alloc]init];
self.place.name = nameString;
NSLog(#"%# name???", self.place.name);
[self.placeObjectsArray addObject:self.place];
self.place.name is not nil, and still, array is empty. Well, its not true, it not empty but, it only contains (null) objects. How to fill array with objects of my class?
Any advice would be appreciated, thanks!
UPDATED:
I init array like this -
-(id)initWithDelegate:(id)delegateObj{
...
self.placeObjectsArray = [NSMutableArray array];
...
return self;
}
UPDATED: Now, when i try to init-alloc array in same method (instead of setting #property and strong relation) i can see it in NSLog. I wonder why it won't happen when i use my array, that set as property..
You need to alloc-init your Mutable Array ;
NSMutableArray *myArray = [[NSMutableArray alloc]initWithObjects:self.place,nil];
or simply
NSMutableArray *myArray = [[NSMutableArray alloc]init];
Then you could add objects with a for loop or whatever you need, using the following :
for ( YOURINSTRUCTION )
{
[myArray addObject:YOUROBJECT]
}
I recommend to lazy instantiate the array, that way it will only get instantiated when really needed. Since you are setting the array as a property, you can override the getter method for it like this:
- (NSMutableArray *)placeObjectsArray
{
if (!_placeObjectsArray) _placeObjectsArray = [[NSMutableArray alloc] init];
return _placeObjectsArray;
}
With this, you can call [self.placeObjectsArray addObject:self.place] anywhere in your code and the array will always be initialized when needed.

Why do objects not get added to my array?

I have two custom classes: FSGame and FSEvent. FSGame has an ivar that should be an NSMutableArray of FSEvent objects.
Here's FSGame.h:
#interface FSGame : NSObject
#property (strong, nonatomic) NSMutableArray *players;
#property (strong, nonatomic) NSString *startTime;
#property (strong, nonatomic) NSString *endTime;
#property (strong, nonatomic) NSMutableArray *gameEvents;
#end
And here's my FSEvent.h:
#interface FSEvent : NSObject
#property NSInteger ID;
#property NSInteger pointTo;
#end
I use
#property (strong, nonatomic) FSGame *game;
to keep an instance of FSGame in my AppDelegate. Then, in my application:didFinishLaunchingWithOptions: I create an instance of FSGame so it can be filled throughout the "game".
_game = [[FSGame alloc] init];
Then, in one of my view controllers, I alloc and init an instance of FSEvent and then attempt to add it to the .gameEvents array:
[appDelegate.game.gameEvents addObject: event];
But something seems to go wrong here because if, from another view controller, I try to get access to that object, it will be nil:
FSEvent *previousEvent = [appDelegate.game.gameEvents lastObject];
if (previousEvent == nil) {
NSLog(#"previousEvent is NIL!");
}
What am I missing here?
It's hard to say with the code that's been shown, but it's possible that you never create the mutable array gameEvents and it's nil when you try to access it or add events to it. Make sure you're creating it somewhere, probably in FSGame's -init:
_gameEvents = [NSMutableArray array];
You need to instantiate the arrays, this does NOT happen when you use
_game = [[FSGame alloc] init];
Try using a lazy instantiation in your FSGame class, add this code
-(NSMutableArray *)gameEvents{
if(!_gameEvents){
_gameEvents = [NSMutableArray array];
}
return _gameEvents;
}
And that should be enough, also make sure of accesing by self.gameEvents and not by _gameEvents!
_game.gameEvents is a pointer to a mutable array. It is not initialized, so its value is nil. There are no any mutable array to add objects to.
You can create it when needed writing this method in FSGame.m:
-(NSMutableArray*) gameEvents {
if ( ! _gameEvents ) {
_gameEvents = [NSMutableArray array];
}
return _gameEvents ;
}

NSString working once then not working

This is my first time using this site and I am quite new to Objective-c. I'm sure this is a simple question but for some reason I am having a lot of issues. The app is designed to have the user enter a string via textfield, then it will pick the rest of the sentence and display it. The issue appears to be that my *name will be retained after the keyboard method and work once in the changelabel method. Then if i press the button again, invoking the changelabel method, the name appears to have been released and crashes the app.
#import
#import "Array.h"
#interface RandomBoredViewController : UIViewController {
UILabel *label;
UIButton *button;
UITextField *textField;
Array *array;
NSString *name;
NSString *description;
NSMutableString *whole;
}
#property (nonatomic, retain) IBOutlet UILabel *label;
#property (nonatomic, retain) IBOutlet UIButton *button;
#property (nonatomic, retain) IBOutlet UITextField *textField;
#property (nonatomic, retain) Array *array;
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSString *description;
#property (nonatomic, retain) NSMutableString *whole;
-(IBAction) keyBoard;
-(IBAction) changeLabel;
#end
and my .m
#import "RandomBoredViewController.h"
#implementation RandomBoredViewController
#synthesize label;
#synthesize checker;
#synthesize button;
#synthesize textField;
#synthesize array;
#synthesize name;
#synthesize description;
#synthesize whole;
-(IBAction) changeLabel {
NSLog(#"Button being pushed");
description = [array getString];
NSLog(#"%#",description);
NSLog(#"%#",name);
name = [NSString stringWithString:name];
whole = [NSMutableString stringWithString:name];
NSLog(#"%#",name);
NSLog(#"%#",whole);
[whole appendString:description];
NSLog(#"%#",name);
NSLog(#"%#",whole);
label.text = whole;
NSLog(#"%#",name);
}
-(IBAction) keyBoard {
name = [NSString stringWithString:textField.text];
NSLog(#"%#",name);
label.text = [NSString stringWithString: name];
[textField resignFirstResponder];
}
- (void)viewDidLoad {
[super viewDidLoad];
array = [[Array alloc]init];
[array createArray];
NSLog(#"%i",[array arrayCount]);
whole = [[NSMutableString alloc]init];
name = [[NSString alloc]init];
}
- (void)dealloc {
[super dealloc];
[label release];
[button release];
[textField release];
[array release];
//[name release];
[description release];
}
#end
You are setting name to an autoreleased instance of NSString, this is probably what's causing your app to crash.
Use
self.name = [NSString stringWithString:textField.text];
Your synthesized mutator will retain the NSString and prevent it from being released.
Taking one thing in microcosm, the code you've posted creates two things named name — an instance variable and a property.
Instance variables are directly accessed storage. They have no behaviour.
Properties are named attributes accessed via getters and setters. So they may have arbitrary behaviour. They may report the values of instance variables or values calculated from instance variables or values calculated or obtained by any other means. Relevantly, the setters may retain, assign or act in any other way.
Instance variables may be accessed only by the instance of a class they belong to. Properties are usually intended to be accessed by anyone.
Since retaining is a behaviour and you've ascribed it to your name property, setting something to it would result in a retain. Instance variables can't have behaviour, so setting a value to it doesn't result in a retain or anything else.
As a result, this line:
name = [NSString stringWithString:name];
Creates a new string and returns a non-owning reference. Which means it'll definitely last for the duration of this autorelease pool (ie, you explicitly may pass it as an argument or return it safely, assuming you haven't taken manual control of your autorelease pools).
You store that reference to your instance variable. Instance variables don't have behaviour so the reference is stored but you still don't own that object. It's still only safe to use for the duration of that autorelease pool.
So when you access it in that method it's safe. When you access it later it's unsafe.
If instead you'd gone with:
self.name = [NSString stringWithString:name];
Then you'd have set that string to be the new value of the property. Because your property has the retain behaviour, you'd subsequently have completely safe access to the string object, until you say otherwise.
Because you've got a property with exactly the same name as an instance variable, you could subsequently access it either as just name or as self.name. Similarly you could have stored directly to the instance variable rather than via the property if you'd ensured you had an owning reference manually.
As suggested above, use of ARC is a way to get the compiler to figure all this stuff out for you.
That issue is what causes your code to crash — you end up trying to access a reference that has ceased to be valid. If you'd taken ownership of it then it would have continued to exist at least as long as you kept ownership.
try using self.name
sometimes this stuff confuses me as well and for that you might want to consider using arc in which case most of this stuff can be avoided.
when using properties you should always use self.propertyName vs propertyName (only), it uses the accessors (get propertyName, set propertyName) as opposed to directly accessing that pointers value.
take in mind there are 2 exceptions to the rule, init and dealloc which should NOT use self.
self.name = [NSString stringWithString:name];
you technically should also have an init method
to initialize your variables, and i believe you should call [super dealloc] last not first in your dealloc method, but thats not your problem and might not matter (just what I do when I dont use arc)
When you change your instance variable in changeLabel, you should release the previous value and retain the new one. You may use the accessors to perform the memory management stuff for you. Also, I think you should invoke [super dealloc] after releasing the instance variables in your implementation of dealloc.
If you're not familiar with Cocoa memory management (and even if you are), the best is to enable ARC (Automatic Reference Counting) and let the compiler deal with it.

Another Memory Management Issue

My app runs well until I stop it and restart - whereupon the archive file - highScores.archive is present. Then, the app balks at encoding - I get a EXC_BAD_ACCESS at the first line (for a long time, it didn't happen until I got to the date object I was encoding.
My guess is that I need to put in a retain in a couple of places, but I don't know where.
The code:
FlipHighScores.h
...
#interface FlipHighScores : NSObject <NSCoding> {
//NSString *themeChosen;
NSInteger newHighScore;
NSInteger newScoreStartLevel;
NSInteger newScoreFinishLevel;
NSDate *scoreDateCreated;}
#property (copy, nonatomic) NSString *themeChosen;
#property (nonatomic) NSInteger highScore;
#property (nonatomic) NSInteger scoreStartLevel;
#property (nonatomic) NSInteger scoreFinishLevel;
#property (nonatomic, readonly, strong) NSDate *scoreDateCreated;
...
FlipHighScores.m
...
#synthesize themeChosen = _themeChosen;
#synthesize highScore = _highScore;
#synthesize scoreStartLevel = _scoreStartLevel;
#synthesize scoreFinishLevel = _scoreFinishLevel;
#synthesize scoreDateCreated = _scoreDateCreated;
...
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_themeChosen forKey:#"_themeChosen"];
NSLog(#"Theme Chosen is %#", _themeChosen);
[aCoder encodeInt:_highScore forKey:#"_highScore"];
[aCoder encodeInt:_scoreStartLevel forKey:#"_scoreStartLevel"];
[aCoder encodeInt:_scoreFinishLevel forKey:#"_scoreFinishLevel"];
NSLog(#"Date Created in encodeWithCoder is %#", _scoreDateCreated);
[aCoder encodeObject:_scoreDateCreated forKey:#"_scoreDateCreated"];}
-(id)initWithCoder:(NSCoder *)aDecoder {
if (self) {
_themeChosen = [aDecoder decodeObjectForKey:#"_themeChosen"];
_highScore = [aDecoder decodeIntForKey:#"_highScore"];
_scoreStartLevel = [aDecoder decodeIntForKey:#"_scoreStartLevel"];
_scoreFinishLevel = [aDecoder decodeIntForKey:#"_scoreFinishLevel"];
_scoreDateCreated = [aDecoder decodeObjectForKey:#"_scoreDateCreated"];
}
return self;}
-(NSString *)description {
NSDate *date = _scoreDateCreated;
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setDateStyle:NSDateFormatterShortStyle];
NSString *dateString = [dateFormatter stringFromDate:date];
//NSLog(#"dateString from description is %#", dateString);
NSString *descriptionString = [[NSString alloc] initWithFormat:#"%d %# S:%d F:%d D:%#", _highScore, _themeChosen, _scoreStartLevel, _scoreFinishLevel, dateString];
return descriptionString:}
What I find confusing is that if I delete the save file - highScores.archive and run the app, it runs without problem. I stop and kill the app and then start it again - the first time encoding is called it crashes.
At the line where I encode the themeChosen object. I have read a few posts about decoding issues being fixed with a "retain" or changing to . format (why that would help, I don't really understand). But this is encoding. Decoding will probably be the next question...
I am not using ARC on this project. Maybe when I rebuild the whole thing from scratch...
Oh, I forgot to mention that everything was running smoothly as far as I had tested until I added in tracking the Theme variable. Then things went a smidge awry as mentioned here.
I think your problem is in your -initWithCoder. You're taking the result of -decodeObjectForKey: and directly assigning it to the synthesized ivar. Methods that don't have the word "copy" in their names are generally assumed to return autoreleased objects.
If you directly assign an autoreleased object to a variable, that object will be released in the next run loop, will dealloc itself, and now your variable points to junk memory. When you try to access it, you'll get an exec_bad_access.
What you should be doing is taking advantage of the accessor methods that #synthesize creates for you. Instead of
_themeChosen = [aDecoder decodeObjectForKey:#"_themeChosen"];
you should write
[self setThemeChosen:[aDecoder decodeObjectForKey:#"_themeChosen"]];
or, if you positively must use an equal sign, you could use the syntactic sugar of "dot notation":
self.themeChosen = [aDecoder decodeObjectForKey:#"_themeChosen"]
Which will ultimately be translated into close to the same thing.
The key is: the synthesized setter does more than simply assign the object to the ivar. It also retains the object (actually, in this case, it copies the object because you specified copy in your #property declaration). This is only one of the many, many, many reasons you should never access ivars directly and always use accessors -- especially now that they're essentially written for you automagically by #property/#synthesize.
UPDATE:
You're going to find you have trouble with scoreDateCreated seeing as you declared it as being readonly in its #property declaration. Why's it read only? It doesn't appear to be a derived value, so you're clearly going to have to assign something to it.
If you want it read/write in your object, but only want to expose a read only interface, you can redeclare the #property as read/write in an anonymous category at the top of FlipHighScores.m. So it looks read only to anything that includes the header, but is actually read/write inside your object's implementation.

Resources