Adding custom class object to an NSArray updates existing objects - ios

I've got a UIView in a viewController:
myView = [[rhythmUIView alloc] initWithFrame:CGRectMake(0,0,100,100)];
[self.view addSubview:myView];
The UIView exposes an NSArray in its .h:
#interface rhythmUIView : UIView
#property () NSMutableArray* myHits;
#end
And instantiates and initializes it in the UIView's .m:
NSMutableArray* myHits;
myHits = [[NSMutableArray alloc] init];
To which I add objects of a custom class:
#interface hits : NSObject
#property () double hitTime;
#property () float xPosition;
#end
in the viewController.m using
hits *thisHit;
thisHit = [[hits alloc] init];
<set thisHit's properties>
[myView.myHits addObject:thisHit];
All of that works - no compile or runtime errors, but when I change the values of the thisHit object in preparation to add a new object to the myHit array, it updates the value of every object that was previously inserted using thisHit.
This seems like an attribute problem, so I added a property to the custom class:
#property (copy) NSNumber* test;
And set it with:
thisHit.test = [NSNumber numberWithFloat:arc4random()%100];
Before the addobject.
But it also modifies every row when I touch thisHit.
I didn't expect adding "copy" to the array to work:
#property (copy) NSMutableArray* myHits;
And it didn't. Same results.
I even tried adding this to the ViewControoler.m:
#property (copy) hits* thisHit;
Same results.
Tried insertObject:atIndex: instead of addObject:
Same results.
I messed around with strong and weak in desperation, but then it actually started crashing.
Finally, learning from my last post, I tried moving the instantiation of the array from the UIView.m to the UIView creation in the viewController.m:
myView.myHits = [NSMutableArray new];
I had high hopes for that one, but again, no compile or runtime errors, but it was worse. The addobject doesn't actually do anything - the nsarray.count remains at zero.
Help please?
Thanks!

but when I change the values of the thisHit object in preparation to add a new object to the myHit array, it updates the value of every object that was previously inserted
This is the problem. You should be creating a new hits object for each member of the array. When you add an object to an array, the array simply adds a pointer to that object. Hence, you are repeatedly adding the same object to the array. So every time you change that object, every object in the array appears to change.
If you only call thisHit = [[hits alloc] init]; once then there is only one hits instance, and you are adding that single instance to the array multiple times.

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.

Access NSMutableArray from another class - Objective C

I have a main ViewController that contains a desginated class. Within that ViewController there is a Container that is linked to an embed ViewController. Within that embed ViewController I am creating an NSMutableArray. I am not trying to access that array inside the main ViewController. I know that if I use:
create_challenge_peopleSelect *myScript = [[create_challenge_peopleSelect alloc] init];
NSLog(#"%#",myScript.selectedCells);
The NSLog will output null because I am creating a new ViewController and that gets rid of the already set array. So my question is how can I access that array without overwriting it?
UPDATE:
Heres where the NSMutableArray is being created:
create_challenge_peopleSelect.h:
#property (strong, nonatomic) NSMutableArray *selectedCells;
create_challenge_peopleSelect.m:
if([selectedCells containsObject:label.text])
{
cell.accessoryType = UITableViewCellAccessoryNone;
[selectedCells removeObjectIdenticalTo:label.text];
}
else
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[selectedCells addObject:label.text];
}
This class is the container class off the main ViewController
No I want to access the selectedCells within my main ViewController, I have been doing things such as:
create_challenge_peopleSelect *myScript = [[create_challenge_peopleSelect alloc] init];
I would prefer to stay away from the App Delegate If possible.
You seem to be unclear on the difference between classes and instances. OK, so, say we have two NSArrays:
NSArray *a = [[NSArray alloc] initWithObjects:#"hello", #"I", #"am", #"an", #"array", nil];
NSArray *b = [[NSArray alloc] initWithObjects:#"so", #"am", #"I", nil];
If I do a.count, I'll get 5 as the answer because the array contains five objects. Meanwhile, if I do b.count, I'll get 3, because that array contains three objects. It isn't that creating b "gets rid of the already set count". They are separate objects completely unrelated to each other.
Your view controller class is the same way. When you create a different instance, it doesn't overwrite the old one -- it's just not the same object. In order to use the original view controller object, you need to get a reference to it.
So how do you get a reference to it? Well, the general answer is you design your app so that the two objects know about each other. There are lots of specific ways to accomplish this. A lot of people will say "Just stick a reference in the app delegate." That is one thing you can do, but it's not always the best choice. It can get out of control if you just stick everything in your app delegate. Sometimes it's the right answer, often other things are the right answer. Another approach is to have an object that knows about both of those objects introduce them to each other. But sometimes there is no such object. So it's situational.
Basically, instead of creating a new view controller, you need to maintain a pointer to the original.
I suggest storing an instance of your UIViewController in the AppDelegate in order to retain the particular instance of the view controller you've created by making it a global variable.
ex. In the App Delegate.h
#import "ViewController.h"
#class ViewController;
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (nonatomic) ViewController *viewController;
Then from whatever view controllers' .m's from which you need to read/write to the variable, create a pointer to the application's app delegate, ex:
#import "AppDelegate.h"
#interface WhateverViewController ()
AppDelegate *mainDelegate;
- (void)viewDidLoad {
mainDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
}
So wherever you first create that view controller in your code (before ever using it), initialize it using this global variable. ex. If you're using xibs:
mainDelegate.viewController = [[UIViewController alloc] initWithNibName:#"ViewController" bundle:nil];
[self.navigationController pushViewController:mainDelegate.viewController animated:YES];
ex. If you're using storyboards:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"StoryboardName" bundle:nil];
mainDelegate.viewController = [storyboard instantiateViewControllerWithIdentifier:#"viewControllerID"];
[self.navigationController pushViewController:mainDelegate.viewController animated:YES];
(This is assuming it's in a place other than the app delegate in which case the pointer to the App Delegate isn't needed.)
Then when accessing the array from another UIViewController use
mainDelegate.viewController.array
To access the NSMutableArray from one class to another class use following code.
In the first view controller in which u have declared the object of NSMutableArray, declare the property and synthesize for the same as below,
//In FirstViewcontroller.h class,
#property (nonatomic, strong) NSMutableArray *arrData;
//In FirstViewcontroller.m class
#synthesize arrData;
Also FirstViewcontroller object should be global so you can create the object of FirstViewcontroller in app delegate file.
//appdelegate.h
#property (nonatomic, strong) FirstViewcontroller *objFirst;
//appdelegate.m
#synthesize objFirst;
FirstViewcontroller *objFirst=[[FirstViewcontroller alloc]init];
Now in SecondViewcontroller in which you have to access array,
create the share object of Appdelegate file
//SecondViewcontroller.m
AppDelegate *app = (AppDelegate*)[[UIApplication sharedApplication] delegate];
Then use will get the required array as below,
app.objFirst.arrData
This is your required array I hope it will help you.
The basic idea here is that in your original class, the array is referred to by a pointer. Your original class would allocate it and presumably load it. Other parts of your program can be handed the contents of the property, which is a pointer, assign that to their own pointer holder, and use it as if you had declared it there. Please use the above code;
MyClass *aClass = [[MyClass alloc] initWithMyInitStuff];
NSMutableArray *ThatArray = aClass.MyArray;
NSLog("Count of ThatArray: %d", [That.Array count]);
What you've done in the code provided is set a public property for a mutable array...
#property (strong, nonatomic) NSMutableArray *selectedCells;
The NSMutableArray is not "created" by setting that property. At some point in your code you also have to create the NSMutableArray by initialising...
NSMutableArray *selectedCells = [[NSMutableArray alloc] init];
or by using a convenience method such as...
NSMutableArray *selectedCells = [NSMutableArray arrayWithCapacity:(NSUInteger)<initialising capacity>];
or
NSMutableArray *selectedCells = [NSMutableArray arrayWithArray:(NSArray *)<initialising array>];
Initialising an NSMutableArray is often done only once. If it is repeated, the contents are overwritten against the property used to point to the array. As such, a useful location for this is often within the viewDidLoad view controller lifecycle method.

How do i save strings to an array and display the strings in an label?

I'm a rookie at iOS and Objective-C programming, trying (HARD) to learn it, so i've enrolled in a basic course. I'm trying to implement two IBAcions in my ViewController.m file but i seem to have problems regarding my way of thinking about this problem and the difficulties understanding the scope, e.g what i can and cannot call and such.
One of this IBActions will throw a dice, getting the right images, and another that will save the outcome and display the latest 10 throws.
Please do not get discouraged by what seems to be a lot of code.
These are the following properties i've declared in the #interface(.h file)
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIImageView *dieOne;
#property (weak, nonatomic) IBOutlet UIImageView *dieTwo;
#property (weak, nonatomic) IBOutlet UILabel *latestTenThrows;
#property (nonatomic) NSUInteger dieValue1;
#property (nonatomic) NSUInteger dieValue2;
#property (nonatomic) NSUInteger totalValue;
#property (nonatomic) NSMutableArray *rolledDiceArray;
#property (nonatomic) NSMutableString *rolledDiceString;
#end
And this is my implementation
- (IBAction)throwDice {
NSMutableArray *dieArray = [[NSMutableArray alloc] initWithObjects:
#"one", #"two", #"three", #"four", #"five", #"six", nil];
NSUInteger dieIndex1 = arc4random_uniform(6);
NSUInteger dieIndex2 = arc4random_uniform(6);
UIImage *image1 = [UIImage imageNamed:[dieArray objectAtIndex:dieIndex1]];
UIImage *image2 = [UIImage imageNamed:[dieArray objectAtIndex:dieIndex2]];
self.dieOne.image = image1;
self.dieTwo.image = image2;
So far so good, the right images are showing but here is where it gets tricky for me. I am trying to "save" the rolled dices value and show them in a lable like this: 5 + 2 = 7 in the view, and it should diplay the latest 10 throws.
self.dieValue1 = (dieIndex1+1);
self.dieValue2 = (dieIndex2+1);
self.totalValue = self.dieValue1 + self.dieValue2;
NSMutableString *rolledDiceString = [[NSMutableString alloc] init];
[rolledDiceString appendString:[NSString stringWithFormat:
#"%d + %d = %d",
self.dieValue1,self.dieValue2,self.totalValue]];
How will i get permission to this array in my other functicion, - (IBAction)saveThrow{} below?
NSMutableArray *rolledDiceArray = [[NSMutableArray alloc]init];
[rolledDiceArray addObject:rolledDiceString];
If i add this NSLog call i will only get 1, no matter how many times i hit "Roll dice" in the interface", which means that when i hit "Roll dice" in the interface it's not putting the values, created into a string, in the array correctly.
How do i add the strings correctly to my array so that i later can retrieve that information from another IBAction? And do i have permission to this array in my function below?
NSLog(#"%d",rolledDiceArray.count);
} // End for -(IBAction)throwDice{
In my second and last IBAction, i'm trying to add the strings to the label, but having issues here since my array doesn't getting the strings.
- (IBAction)saveThrow {
NSMutableArray *savedThrowsArray = [[NSMutableArray alloc] init];
[savedThrowsArray addObject:self.rolledDiceArray];
self.latestTenThrows.text = [// Latest 10 items (strings) in savedThrowsArray];
}
Don't flail; think what you are doing. Your words (in your code) have meaning. The computer can only do what you tell it to do...! So, let's look:
NSMutableArray *rolledDiceArray = [[NSMutableArray alloc]init];
[rolledDiceArray addObject:rolledDiceString];
Every time you run that code, you are creating a completely new empty mutable array (that is what = [[NSMutableArray alloc] init] means in the first line). Therefore no matter how many times you run it, the resulting array will contain only one object (the object you add in the second line with addObject).
NSMutableArray *savedThrowsArray = [[NSMutableArray alloc] init];
[savedThrowsArray addObject:self.rolledDiceArray];
Same deal.
And meanwhile, you've got these completely different instance variable sitting unused:
#property (nonatomic) NSMutableArray *rolledDiceArray;
#property (nonatomic) NSMutableString *rolledDiceString;
But none of your code ever talks to / about them. They are just sitting there going to waste. You need to initialize them and put your data in them - not in these local automatic variables that you keep creating and throwing away.
I think the error is here:
NSMutableString *rolledDiceString = [[NSMutableString alloc] init];
this creates a variable local to the method which overrides the class property. So when it goes out of scope (i.e. the method returns) it is deallocated.
Simply replace with
self.rolledDiceString = [[NSMutableString alloc] init];
and the same happens for the array - you should allocate it once (in init or viewDidLoad) and just use
[self.rolledDiceArray addObject:self.rolledDiceString];

Lazy Instantiation not working iOS

I've declared a property in the .h file called cellTitles. In my .m file, I have a method as follows:
-(NSArray *)cellTitles
{
if(!_cellTitles){
_cellTitles = [[NSArray alloc] initWithObjects:#"several strings", nil];
NSLog(#"Home Array Created");
}
return _cellTitles;
}
But the array is not created when I refer to _cellTitles or self.cellTitles. I have several NSLogs that all say the array has 0 objects. Do I need more than this. Some answers have said I need to synthesize, but as I understand it, that is no longer necessary.
Make sure your property is assigned strongly.
#property (nonatomic, strong) NSArray *cellTitles;

where to release NSArray used in different methods of same class

In my iOS project i am creating an NSArray that can contains integer values. There are several functions in this class that do their task on that NSArray. So i thought to create NSArray as private attribute of that class. In viewDidLoad i am allocating memory to this array i.e
myArray = [[NSArray alloc] init];
myArray will be used in several method of this class. When i analyze my code it shows memory leak as i am allocating myArray and not releasing it.
If i write [myArray release] in dealloc or viewDidUnload warning still there. If i release myArray in last method of class that is using this array, xCode wont allow me to do.
Here is How i am declaring it in my class
.h file
#interface FightVC : UIViewController
{
NSArray *myArray;
}
I want to know what is possible solution of this. other then using autorelease . I don't want to make it public so i am not writing this array as property.
Thanks in advance
Using a private #property as mentioned in the other answer is probably the nicest and cleanest way to do this. Raw instance variables aren’t a very modern way of doing things. However, if you are going down that road, you can implement a getter for your ivar in which you release the old ivar and retain the new one:
- (void)setMyArray:(NSArray *)array {
[myArray release];
myArray = [array retain];
}
That’s the typical pattern anyway (which is what having an #property does for you automatically).
After that, you can create the array, use the setter, and then release the object:
NSArray *newArray = [[NSArray alloc] init];
[self setMyArray:newArray];
[newArray release];
That should keep the analyzer from squawking at you. A few things stick out to me though:
1) [[NSArray alloc] init] isn’t likely to do what you want it to do. It’s going to create an empty, immutable array. You probably either want an NSMutableArray, or you want to instantiate it with objects already in it using a different initializer.
2) NSArrays aren’t really suited for holding integers themselves, they hold objects. You can either use an NSPointerArray or you can put the integers into NSNumbers and put them into an NSArray.
To make a property private, you have to create an anonymous category.
In your .m file:
#interface myClass ()
#property (nonatomic, retain) NSArray *myPrivateArray;
#end
#implementation myClass
// Class code here
#end
To release your array, simply set the property to nil.
self.myPrivateArray = nil;

Resources