In the iPhone objective-c world, I've seen this pattern everywhere and I use it myself all the time without really understanding what is going on:
In Test.h
#interface Test: UIViewController
{
NSMutableArray *testArray;
}
#property (retain, nonatomic) NSMutableArray *testArray;
And in Test.m
#implementation Test
#synthesize testArray
- (void) viewDidLoad
{
// why do we do this?
NSMutableArray *init = [[NSMutableArray alloc] init];
self.testArray = init;
[init release];
[self.testArray addObject: #"A"]; // why can't I do this directly?
...
}
- (void) dealloc
{
[testArray release];
[super dealloc];
}
My question is: if testArray has a retain on it when it's declared in the property, why do we need to create a new NSMutableArray init object, assign that to testArray and release? Why can't I just start using testArray in viewDidLoad without doing anything else?
I know there's some debate over the best way of doing this (creating a new object, or using an autorelease object), but in both cases, we end up with testArray with a retain count of 1. Which I believe the 'retain' property already gives it. So why the need to create this init object?
The 'retain' property doesn't automatically create an NSMutableArray for you. Rather, it simply indicates that whenever you do assign something to that property, it will be retained.
If your code were this:
- (void) viewDidLoad
{
[self.testArray addObject: #"A"];
}
Then self.testArray would be nil, and thus it would be essentially a no-op. Until you assign something to self.testArray, it's empty.
Here's what's going on.
- (void) viewDidLoad
{
// we need to assign an NSMutableArray to self.testArray.
NSMutableArray *init = [[NSMutableArray alloc] init];
// The array has been retained once (by the call to |alloc|)
self.testArray = init;
// The array is assigned to a property with the 'retain' attribute
// Thus, the array has now been retained twice
[init release];
// We release the array, so it now is retained once.
// We now have an array in self.testArray, so we can add something to it.
[self.testArray addObject: #"A"];
}
The "retain" in the #property directive specifies that the setter should retain the input value instead of simply copying the value. It has nothing to do with allocating (setting aside memory) and initializing (constructing the object) the object. retain on the #property directive simply increments the retain count when the setter is called (which alllows you to do something like self.myobject = something without specifically calling retain.
Related
I'm making a program where one Class (classA) generates a random number and adds it to a mutable array. A view controller (viewControllerA) calls a method from classA and receives the array of random numbers and stores it in its own array.
I have another class (classB) that needs the same array. After viewcontrollerA is finished doing what it needs to do with the array, it calls the setter method for an array in classB. I call NSLog in the setter and getter methods for the array in classB to check to see if it loads.
-(void)setRandomNumberArray:(NSArray *)randomNumberArray{
_randomNumberArray = randomNumberArray;
NSLog(#"%# setter", _randomNumberArray);
}
-
-(NSArray *)randomNumberArray{
if (!_randomNumberArray) {
_randomNumberArray = [[NSArray alloc] init];
}
NSLog(#"%# getter", _randomNumberArray);
return _randomNumberArray;
}
When I call the setter method in viewControlerA, NSLog returns the value of the array.
When I call the getter method in viewControllerB, NSLog prints nothing from the getter method.
2012-05-29 23:57:43.589 SwipeGame[8603:f803] (
) getter
It's obviously setting the array but not retaining it for when i want to get it. What is going on? I've tried multiple other techniques and it always sets the array but doesn't retain it for when i want to "get" the array.
the property for my array is set to retain btw..
UPDATE:
Yes I am using ARC. my property declaration is:
#property (nonatomic, strong) NSArray *randomNumberArray
SOLVED:
Thanks for all your help! It was a problem with instances.
Your setter method does not mention viewControllerB. You are just setting an internal variable. How is viewControllerB going to know about the array having been set?
The easiest way is to just use #properties and #synthesize:
// in A
viewControllerB.array = _array;
As for the retain question: if you use ARC you should not worry about it.
Do you use ARC?
ARC version bellow:
#interface Foo : NSObject {
NSMutableArray *_randomNumberArray;
}
#property (nonatomic, strong) NSMutableArray *randomNumberArray;
#end
#implementation Foo
#synthesize randomNumberArray = _randomNumberArray;
- (void)setRandomNumberArray:(NSMutableArray *)randomNumberArray {
_randomNumberArray = randomNumberArray;
NSLog(#"%# setter", _randomNumberArray);
}
- (NSMutableArray *) randomNumberArray {
if ( _randomNumberArray == nil )
_randomNumberArray = [[NSMutableArray alloc] init];
NSLog(#"%# getter", _randomNumberArray);
return _randomNumberArray;
}
#end
Not ARC version bellow:
#interface Foo : NSObject {
NSMutableArray *_randomNumberArray;
}
#property (nonatomic, strong) NSMutableArray *randomNumberArray;
#end
#implementation Foo
#synthesize randomNumberArray = _randomNumberArray;
- (void)setRandomNumberArray:(NSMutableArray *)randomNumberArray {
[_randomNumber release];
_randomNumberArray = randomNumberArray;
[_randomNumberArray retain];
NSLog(#"%# setter", _randomNumberArray);
}
- (NSMutableArray *) randomNumberArray {
if ( _randomNumberArray == nil )
_randomNumberArray = [[NSMutableArray alloc] init];
NSLog(#"%# getter", _randomNumberArray);
return _randomNumberArray;
}
- (void)dealloc {
[_randomNumberArray release];
}
#end
If you are creating this random number array using an NSMutableArray, and passing that to the setter, the array could be mutated later by the caller (e.g. all items removed) and the array can change from under your feet.
For types like NSArray and NSString which have mutable subtypes, you should declare your property as copy instead of strong. This will ensure the array passed to you cannot be mutated at a later date by somebody else. Copy performance is not a problem because the regular immutable types handle copies very efficiently.
If I have a variable in my view controler
viewcontroller.m
#interface MemoryTestViewController : UIViewController
{
NSMutableArray *array;
}
#end
in my implementation
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *aux = [[NSMutableArray alloc] initWithCapacity:1];
array = aux;
[aux release];
// Do i have to do array release?
}
Do i have to release my variable array somewhere? Theoricaly i havent allocated that variable...
I testes the memory leaks and even if i dont release anything the instruments doesn't detect any leak.
No, you don't need to release. All you do is assign the pointer of aux to your array variable.
array is invalid at the moment where you release aux.
This is probably not as intended. If you want to work with array, you'll have to retain it.
You've already released the array with [aux release]; -- you in fact have the opposite problem to a leak: an over-release.
Assignments in Objective-C are just assignments of pointers; there's no copying or automatic memory management. When you say array = aux;, array now points to the exact same object as aux. If you then get rid of aux by releasing it (and therefore letting it be deallocated), array doesn't point to anything anymore.*
You have a couple of options for fixing this:
(Simplest) Assign the newly-created array directly to array:
array = [[NSMutableArray alloc] initWithCapacity:1];
This gives you ownership of the new array, under the name array. Don't release it until you are done with it (possibly in dealloc; certainly not in this method).
(Best) Create a declared property for array and let that mechanism handle the memory management for you:
#interface MemoryTestViewController : UIViewController
{
NSMutableArray *array;
}
#property (copy, nonatomic, setter=setArrayByMutableCopy) NSMutableArray * array;
#end
#implementation MemoryTestViewController
#synthesize array;
// Properties can't automatically make mutable copies, so you need to create
// your own setter method.
- (void) setArrayByMutableCopy: (NSMutableArray *)newArray {
NSMutableArray * tmp = [newArray mutableCopy];
[array release];
array = tmp;
}
...
*Or, rather, it points to a place where there used to be a valid object, which is a great way to make your program crash.
No. Assigning an object to a variable does not retain it. However if you plan to use that variable for a while, you should retain it and release it when you are done with it.
alloc raised the retain counter to 1 and [aux release] set it to 0
You should add a property:
#property (nonatomic, retain) NSMutableArray *array;
and later in your viewDidLoad:
// wrong, leaks: self.array = [[NSMutableArray alloc] initWithCapacity:1];
array = [[NSMutableArray alloc] initWithCapacity:1];
and yes, somewhat later release it, probably in dealloc..
UIView *view; //1
UISegmentedControl *scopeBar; //2
NSMutableArray *array; //3
#property (nonatomic, retain) IBOutlet UIView *view;
#property (nonatomic, retain) UISegmentedControl *scopeBar;
#property (nonatomic, retain) NSMutableArray *array;
.m
#synthesize view, scopeBar, array;
for (id subView in [view subviews]) {
if ([subView isMemberOfClass:[UISegmentedControl class]]) {
scopeBar = (UISegmentedControl *)subView;
}
}
array = [[NSMutableArray alloc] init];
- (void)dealloc {
}
I think that only the third of the variables has to be released in the dealloc method.
Is that right?
Yes, (array needs to be released) because you alloc it. So, it's programmer's responsibility to release it. So -
- (void)dealloc {
[ array release ] ;
// Any other resources alloc, init, new should be released
}
For more info on what to release, Memory management - ObjectiveC
And I think you will find good suggestions in this question about your query
Why should we release?
Contrary to some of the answers, you have to release your outlet (view) as well, and not only in the dealloc but also in the viewDidUnload, the easiest way is to set it to nil :
self.view = nil;
Also note that if you don't access your properties but your instance variables (i.e. without self. prefix) your retain attribute won't help you and you are not retaining the object. That means that as soon as scopeBar would be removed out of the subViews of the view, it will be released and you end up accessing a zombie.
As a rule of thumb, it's best to use the properties accessor everywhere except in the init methods so that you don't have to deal with the memory management explicitly. Setting them to nil in the dealloc and viewDidUnload in case of outlets should be enough then.
Also, don't do what Jenifer suggested and once you've called a release on a variable, don't set the property to nil, that would overrelease it.
I think that only the third of the variables has to be released in the dealloc method. Is that right?
// no. your dealloc should look like this:
- (void)dealloc {
// note: *not* using accessors in dealloc
[view release], view = nil;
[scopeBar release], scopeBar = nil;
[array release], array = nil;
[super dealloc];
}
// your assignment of `scopeBar` should look like this:
...
self.scopeBar = (UISegmentedControl *)subView;
...
// you want to retain the view, as advertised.
// consider avoiding an ivar if you can easily access it.
// your assignment of `view` should look like this:
...
self.view = theView;
...
// you want to retain the view, as advertised.
// consider avoiding an ivar if you can easily access it.
// your assignment of `array` should look like this in your initializer:
// note: *not* using accessors in initializer
...
// identical to `array = [[NSMutableArray alloc] init];`
array = [NSMutableArray new];
...
// and the assignment of `array` should look like this in other areas:
...
self.array = [NSMutableArray array];
...
// you're likely to be best suited to declare your array as
// follows (assuming you really need a mutable array):
...
NSMutableArray *array; // << the declaration of the ivar
...
...
// the declaration of the public accessors.
// note the array is copied, and passed/returned as NSArray
#property (nonatomic, copy) NSArray *array;
...
// finally, the implementation manual of the properties:
- (NSArray *)array {
// copy+autorelease is optional, but a good safety measure
return [[array copy] autorelease];
}
- (void)setArray:(NSArray *)arg {
NSMutableArray * cp = [arg mutableCopy];
// lock? notify?
NSMutableArray * prev = array;
array = cp;
[prev release], prev = nil;
// unlock? notify? update?
}
other answers assume that dangling pointers (e.g., you still hold a pointer to view, although the view may have changed behind your back) are allowable.
they should not be allowed in real programs. they are extremely dangerous, and it can very difficult to reproduce errors they cause. therefore, you must ensure you own a reference to the pointers you maintain/hold.
you should also use the accessors in the public interface for the subclasser's sake - in case they override them. if you don't want to allow/support that, consider simply using a private variable.
As i think you should release and set them nil because you have made them properties so do this:-
in your dealloc
[array release];
self.array=nil;
self.scopeBar=nil;
self.view=nil;
When you synthesize a property (see below)
#interface CelestialBody : NSObject {
NSString *name;
}
...
#interface Planet : NSObject {
NSString *name;
int mass;
CelestialBody *moon;
}
#property(nonatomic, retain) NSString *name;
#property(assign) int *mass;
#property(nonatomic, retain) CelestialBody *moon;
...
#implementation Planet
#synthesize name;
#synthesize mass;
#synthesize moon;
...
You get setters and getters for each of the iVars (i.e.)
[newPlanet setName:#"Jupiter"];
[newPlanet setMass:57];
NSString *closestName = [newPlanet name];
int largestMass = [newPlanet mass];
CelestialBody *newMoon = [[CelestialBody alloc] initWithName:#"Callisto"];
[self setMoon:newMoon];
[newMoon release];
but you also get the ability to release the object using ...
// Releases the object (frees memory) and sets the object pointer to nil.
[self setMoon: nil];
There will of course be deallocs for each Class.
// Moon
-(void)dealloc {
[name release];
[super dealloc];
}
// Planet
-(void)dealloc {
[name release];
[moon release];
[super dealloc];
}
Am I getting this right?
gary
Unless your planet object is declared as a property within some other class, using the retain/copy attributes, you can't release it this way.
When you declare a property using retain/copy, the resulting setter will release the old value and assign the new value, retaining or copying it in the process. If you pass nil, you will release the old value and assign nil, retaining or copying it, and retaining/copying nil is nil, so effectively you end up releasing the old value and assigning nil to the ivar.
This is an acceptable way to release instance variables.
In order to be able to release your newPlanet instance this way, you'd have to have declared it in a class as a property with either retain or copy.
As a further example, since your planet object declares its properties in this way, you could release those using this method.
Or in the Planet class's dealloc method, you could do:
self.name = nil;
This would release name and assign nil to it.
"you also get the ability to release the object"
Yes, as long as you didn't declare it with the assign attribute.
As you probably know, one of the reasons (although perhaps not the primary one) for using declared properties is that you can do:
self.moon = aMoon;
rather than;
[self setMoon:aMoon];
They are equivalent. That means that your deallocation can look like this:
self.moon = nil; // Releases and sets to nil
But remember to never just do:
moon = nil; // Sets to nil, doesn't release
It's very good practice to not only release the object, but to set the variable to nil, as you do, because otherwise some other code could mistakenly try to use the pointer that is left in the variable.
Your example shows the synthesis of one class's ivars (those of Planet) but the use of another (whatever "self" is). Is the "newPlanet" property of "self" in your last example also synthesized as (retain)? If so, then: Yes, setting newPlanet to nil will release whatever self's old "newPlanet" was.
I think you are not getting it right.
After your question update, yes, you can do that, and also:
self.moon = [[CelestialBody alloc] initWithName:#"Callisto"];
and release it later, probably in your dealloc method:
self.moon = nil;
Apple Objective-c 2.0 Properties and Memory Management docs are pretty good. Check Mac Dev Center library.
Can anyone tell me if the NSString instance variable "planetName" needs to be allocated / released by me (as in the example below) or is that done when the class instance is created / allocated?
My understanding is that int and float don't need to be, but not sure about NSString & NSArray ...
#interface PlanetClass : NSObject {
NSString *planetName;
}
- (NSString *)planetName;
- (void)setPlanetName:(NSString *)value;
#end
Like this ...
- (id) init {
[super init];
planetName = [[NSString alloc] init];
return self;
}
- (void) dealloc {
[planetName release];
[super dealloc];
}
** ---------------------------------- **
EDIT: EDIT: Here is another version
** ---------------------------------- **
int main(int argc, const char *argv[]) {
// ** Allocated here
PlanetClass *newPlanet_01 = [[PlanetClass alloc] init];
NSString *newPlanetName = [NSString alloc] init];
// ** Set the instance variable pointer here
newPlanetName = #"Jupiter";
[newPlanet_01 setPlanetName:newPlanetName];
// ** Released here
[newPlanet_01 release];
return 0;
}
the init & dealloc methods would then be like this ...
- (id) init {
[super init];
return self;
}
- (void) dealloc {
// Always release the current copy of planetName
// pointed to by the class instance.
[planetName release]
[super dealloc];
}
The setPlanetName method would look like this ...
- (void)setPlanetName:(NSString *)newPlanetName {
if (planetName != newPlanetName) {
[planetName release];
planetName = [newPlanetName copy];
}
}
PS: I am not using properties or synthesize, I have not gotten that far yet.
cheers -gary-
Your code is valid, but there's probably no reason to initialize planetName to an empty string. One of the nice features of Objective-C is that you can send messages to a nil object with no consequence. If your class is initialized and you never call -setPlanetName:, planetName will be nil (instance variables are always initialized to nil), so when your -dealloc method calls [planetName release], nothing will happen.
In general, the best practice is to use -copy when setting an NSString instance variable, and -retain when setting most other objects as instance variables. As such, your -setPlanetName: method would look something like this:
- (void)setPlanetName:(NSString *)newPlanetName {
NSString *tempPlanetName = [newPlanetName copy];
[planetName release];
planetName = tempPlanetName;
}
You still have an issue with your new code.
In your main function, you release newPlanetName but this is a little wrong. Your PlanetClass retained it with its setPlanetName: method, but your PlanetClass never releases it again unless the name of the planet changes. It should not be up to the caller of setPlanetName: to keep hold of the string, it is your classes responsibility to deal with it appropriately.
Your old dealloc method is correct. It should release the planet's name because your PlanetClass no longer needs it. Your main method should not release the planet's name because the string returned by stringWithString: does not belong to you, and you give it to PlanetClass to take care of.
So, keep your old dealloc method, and remove the [newPlanetName release] from the main function and you should be alright from there.
As a shortcut, you can even call [newPlanet_01 setPlanetName:#"Jupiter"] and do away with the newPlanetName variable altogether in your main function.
planetName is a pointer which, like an int or float, does not need to be allocated or initialized.
Just like you can assign values to an int or float, you can point planetName at different instances of a string, or it can point at nothing.
When you init your class, planetName will be nil (not pointing at anything). If you point planetName to an instance of a string, you have to retain that string, and release in dealloc.
In other words, this:
planetName = [[NSString alloc] init];
is unnecessary and meaningless.
In your setPlanetName method you would need to release the existing string that planetName is pointing to, assign planetName to the new string, and then retain the new string.
Your dealloc method is correct.
Your code looks good. NSObject subclasses (NSString included) need to have their memory mananged by the object that owns them. In this case, that owner is PlanetClass.