My purpose: making an API call to a server, and getting back from them is an array of data named dataArr and I want to store these data to another array for later need.
What I am doing so far is
myClass.h:
#propery ( nonatomic, retain ) NSArray *dataList;
myClass.m:
#implementation myClass
-(void)receivedData:(NSArray*) dataArr {
// ???
}
To fill in at line 3, I have two options, option A:
dataList = dataArr;
or option B:
[dataList release];
[dataArr retain];
dataList = dataArr;
I think option A is the right way to do it because dataList is declared as retain in the header file. Therefore, the setter will make sure to release a current array (dataList) and reain a received array (dataArr) as well
I just want to double check that I am on the right path.
Please correct me if I have just made a mistake in the middle. Thanks
Any comments are welcomed.
dataList = [dataArr];
this is not valid Objecitve-C. If you wanted to write
dataList = dataArr;
that's still a no-go, as you're acessing the instance variable directly, not through the property setter, that is, your array won't be retained and it will badly crash.
[dataList release];
[dataArr retain];
dataList = dataArr;
is wrong again. If dataList was the same as dataArr, and the reference of the object (self) was the last reference to it, then it would get deallocated, breaking the following retain message, and most likely crashing again.
If you have a property setter (which you have), simply write
self.dataList = dataArr;
this will retain the array correctly. By the way, the implementation of the setter is something like your last method, but it checks either for inequality:
- (void)setDataList:(NSArray *)dl
{
if (dataList != dl)
{
[dataList release];
dataList = [dl retain];
}
}
or pre-retains the object to be set:
- (void)setDataList:(NSArray *)dl
{
[dl retain];
[dataList release];
dataList = dl;
}
add #synthesize dataList; so the compiler can generate the default setter
then in line 4 add:
self.dataList = dataArr;
The default setter will take charge of releasing and retaining in a correct manner
Related
I'm trying to learn Objective C. I came across the following code which the compiler generates behind the scenes for #property(nonatomic, retain) NSString* myField
-(NSString*) myField
{
return myField_; //assuming myField_ is the name of the field.
}
-(void) setMyField:(NSString*) newValue
{
if(newValue != myField_)
{
[myField_ release];
myField_ = [newValue retain];
}
}
Now my question is; Why to call retain on newValue? Instead the following syntax should be used:
myField_ = newValue;
[myField_ retain];
Please advise why the above syntax is not used because as per my understanding, we want to retain the object pointed to by myField_ ?
They're the same (both are correct). You don't copy the object - retain returns the same pointer that was retained, so it's shorter and cleaner to write
ivar = [newObj retain];
than separately assigning and retaining the object.
Both syntaxes are correct. In the first case we also retain the object pointed by myField since we assign [newValue retain] to it.
I have the following variable defined:
#property (nonatomic, retain) NSMutableArray *arraySpeechSentences;
And I am trying to initialise it in the following way:
// Set the array of sentences to the stored array
NSMutableArray *speechSentences = [[NSMutableArray alloc] initWithArray:[tempDict objectForKey:key]];
arraySpeechSentences = speechSentences;
[speechSentences release];
When I try to call [arraySpeechSentences count] the application crashes. However, if I set the variable in the following way:
// Set the array of sentences to the stored array
NSMutableArray *speechSentences = [[NSMutableArray alloc] initWithArray:[tempDict objectForKey:key]];
self.arraySpeechSentences = speechSentences;
[speechSentences release];
I can call [arraySpeechSentences count] perfectly fine. I was under the impression that if you use self. it simply checks to see if variable is already set, and if so it will release the object before assigning it the new value. Have I got this wrong, and if so when should I be using self. to set values?
Thanks for any help,
Elliott
Using a setter (like self.foo = ... or [self setFoo:...]) does release the old value but it also retains the new value, which is needed in the example you give.
The issue is that you're alloc and init'ing your array, and then releasing it. This indicates you no longer need it. So, you should either use the setter (usually preferable) or don't release your array.
If you're not using ARC, you should type
arraySpeechSentences = [speechSentences retain];
because you're accessing the instance variable directly, which means the value of the instance variable arraySpeechSentences will be the address of the speechSentence object, which you just released, so which is an invalid pointer. The semantic you declared in the property doesn't have an effect on the instance variable itself.
When you type self.arraySpeechSentences, you're actually using a shortcut for the setter [self setArraySpeechSentences:speechSentences], which actually retains the value passed as parameter (if you synthesized the property, it is retained because you specified retain in the property declaration; if you wrote the accessor yourself, it is your job to ensure you retained the value).
I'll try to give a detail answer for this.
First when you use #property/#synthesize directive you create getter and setter methods around a variable.
In your case, the variable is called arraySpeechSentences (the compiler will create the variable for you) and you can access these methods (setters and getters) with self..
self.arraySpeechSentences = // something
is the same as
[self setArraySpeechSentences:something]; // setter
And
NSMutableArray* something = self.arraySpeechSentences;
is equal to
NSMutableArray* something = [self arraySpeechSentences]; // getter
In the first snippet of code
NSMutableArray *speechSentences = [[NSMutableArray alloc] initWithArray:[tempDict objectForKey:key]];
arraySpeechSentences = speechSentences;
arraySpeechSentences points to the same object speechSentences points to. But when you do [speechSentences release] you dealloc that object and now arraySpeechSentences is a dangling pointer. You receive a message sent to a deallocated instance I suppose. Try to enable Zombie to see it.
Speaking in terms of retain count, the array has a retain count of 1 when you do alloc-init.
But when you release it, the retain count goes to zero, the object doesn't exist anymore and you have a crash when you try to access arraySpeechSentences.
Instead, when you deal with properties, the policy applied to a variable is important. Since the property use a retain policy, when you set an object
self.arraySpeechSentences = // something
the retain count for the referenced object is increased. Under the hood, saying self.arraySpeechSentences = // something is equal to call the setter like
- (void)setArraySpeechSentences:(NSMutableArray*)newValue
{
// pseudo code here...
if(newValue != arraySpeechSentences) {
[arraySpeechSentences release];
arraySpeechSentences = [newValue retain];
}
}
The second snippet work since the retain count for your object is one when you do alloc-init, becomes two when you call self.arraySpeechSentences = and returns to one when you do the release. This time, the object is maintained alive since it has a retain count of 1.
If you have a property with a retain or copy policy, don't forget to release the object in dealloc like, otherwise you can have leaks.
- (void)dealloc
{
[arraySpeechSentences release];
[super dealloc];
}
To understand how Memory works I suggest to read MemoryManagement Apple doc.
P.S. Starting from iOS 5 there is a new compiler feature, called ARC (Automatic Reference Counting), that allows you to forget about retain/release calls. In addition, since it forces you to think in terms of object graphs, I suggest you to take a look into.
Hope that helps.
If I know that I'm going to use the ivar should I alloc/init it in viewDidLoad like:
if (allPeople_ == nil)
self.allPeople = [NSArray arrayWithArray:[[selectedObject people] allObjects]];
or should I create a getter method and alloc/init in there:
- (Group *)allPeople {
if (allPeople_ != nil)
return allPeople_;
allPeople_ = [NSArray arrayWithArray:[[selectedObject people] allObjects]];
return allPeople_;
}
I'm assuming the getter method, with the if-statement, is for lazy-loading, which in my case I wouldn't need to do because I'm definitely using self.allPeople throughout my code.
Extra Question:
If I use the getter method do I actually have to do it this way?
allPeople_ = [[NSArray arrayWithArray:[[selectedObject people] allObjects]] retain];
I would initialize it whenever you are going to use it.
As for the second question, it depends on how your property is declared if it is declared as retain, and you set it like this:
self.allPeople =
you will not have to send it a retain message, because the synthetized setter will take care of that for you.
Do notice self.allPeople is different than just allPeople, if you don't use self you are not accessing it thru the setter, you are accesing the ivar directly and therefore it won't receieve a retain message.
You might try to make your NSArray an NSMutableArray that way you can alloc init it in your init call. Use property declarations to synthesize your getters and setters. As for putting the people in your array, you can add them to the mutable array every time one is selected
I'm new to cocoa / objective-c and i'm struggeling with the releases of my objects. I have the following code:
gastroCategoryList = [[NSMutableArray alloc] init];
for (NSDictionary *gastrocategory in gastrocategories) {
NSString *oid = [gastrocategory objectForKey:#"id"];
GastroCategory *gc = [[GastroCategory alloc] initWithId:[oid intValue] name:[gastrocategory objectForKey:#"name"]];
[gastroCategoryList addObject:gc];
}
The analyzer shows me that the "gastrocategory" defined in the for is a potential memory leak. But i'm not sure if i can release this at the end of the for loop?
Also at the following code:
- (NSArray *)eventsForStage:(int)stageId {
NSMutableArray *result = [[NSMutableArray alloc] init];
for (Event *e in eventList) {
if ([e stageId] == stageId) {
[result addObject:e];
}
}
return result;
}
The Analyzer tells me that my "result" is a potential leak. But where should I release this?
Is there also a simple rule to memorize when i should use assign, copy, retain etc. at the #property ?
Another problem:
- (IBAction)showHungryView:(id)sender {
GastroCategoriesView *gastroCategoriesView = [[GastroCategoriesView alloc] initWithNibName:#"GastroCategoriesView" bundle:nil];
[gastroCategoriesView setDataManager:dataManager];
UIView *currentView = [self view];
UIView *window = [currentView superview];
UIView *gastroView = [gastroCategoriesView view];
[window addSubview:gastroView];
CGRect pageFrame = currentView.frame;
CGFloat pageWidth = pageFrame.size.width;
gastroView.frame = CGRectOffset(pageFrame,pageWidth,0);
[UIView beginAnimations:nil context:NULL];
currentView.frame = CGRectOffset(pageFrame,-pageWidth,0);
gastroView.frame = pageFrame;
[UIView commitAnimations];
//[gastroCategoriesView release];
}
I don't get it, the "gastroCategoriesView" is a potential leak. I tried to release it at the end or with autorelease but neither works fine. Everytime I call the method my app is terminating. Thank you very much again!
In your loop, release each gc after adding it to the list since you won't need it in your loop scope anymore:
gastroCategoryList = [[NSMutableArray alloc] init];
for (NSDictionary *gastrocategory in gastrocategories) {
NSString *oid = [gastrocategory objectForKey:#"id"];
GastroCategory *gc = [[GastroCategory alloc] initWithId:[oid intValue] name:[gastrocategory objectForKey:#"name"]];
[gastroCategoryList addObject:gc];
[gc release];
}
In your method, declare result to be autoreleased to absolve ownership of it from your method:
NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
// An alternative to the above, produces an empty autoreleased array
NSMutableArray *result = [NSMutableArray array];
EDIT: in your third issue, you can't release your view controller because its view is being used by the window. Setting it to autorelease also causes the same fate, only delayed.
You'll have to retain your GastroCategoriesView controller somewhere, e.g. in an instance variable of your app delegate.
BoltClock's answer is spot-on as to the first part of your question. I'll try to tackle the rest.
Assign is for simple, non-object types such as int, double, or struct. It generates a setter that does a plain old assignment, as in "foo = newFoo". Copy & retain will, as their names imply, either make a copy of the new value ("foo = [newFoo copy]") or retain it ("foo = [newFoo retain]"). In both cases, the setter will release the old value as appropriate.
So the question is, when to copy and when to retain. The answer is... it depends. How does your class use the new value? Will your class break if some other code modifies the incoming object? Say, for example, you have an NSString* property imaginatively named "theString." Other code can assign an NSMutableString instance to theString - that's legal, because it's an NSString subclass. But that other code might also keep its own reference to the mutable string object, and change its value - is your code prepared to deal with that possibility? If not, it should make its own copy, which the other code can't change.
On the other hand, if your own code makes no assumptions about whether theString might have been changed, and works just as well whether or not it was, then you'd save memory by retaining the incoming object instead of unnecessarily making a copy of it.
Basically, the rule, which is unfortunately not so simple sometimes, is to think carefully about whether your own code needs its own private copy, or can correctly deal with a shared object whose value might be changed by other code.
The reason you can release gc after it is added to the gastroCategoryList is that when an object is added to an array, the array retains that object. So, even though you release your gc, it will still be around; retained by the gastroCategoryList.
When you are returning a newly created object from a method, you need to call autorelease. This will cause the object to be released only after the runtime leaves the scope of the calling method, thereby giving the calling method a chance to do something with the returned value.
Note that if your method starts with the word copy or new, then you should not autorelease your object; you should leave it for the calling method to release.
As for copy vs retain vs assign... as a general rule, copy objects that have a mutable version, such as NSArray, NSSet, NSDictionary, and NSString. This will ensure that the object you have a pointer to is not mutable when you don't want it to be.
Otherwise, use retain whenever you want your class to be ensured that an object is still in memory. This will apply to almost every object except for objects that are considered parents of your object, in which case you would use assign. (See the section on retain cycles here).
Also note that you have to use assign for non-object types such as int.
Read through the Memory Management Programming Guide a bit; it's quite helpful.
I have an iPhone app where one view, View A, updates another view in a tab bar, View B, using:
// This works.
- (void) reloadData
{
MyDB * db = _GET_DB_CLASS;
if(data != nil) // data is a property of type NSMutableArray
[data release];
NSMutableArray * d = [db getDataQuery];
data = s; // Don't release since we are not using the accessor. And retain count should be 1.
}
If I do this, it doesn't work (e.g. I update B, then switch to B, crash. I can't see anything useful in the logs either ... ).
NSMutableArray * d = [db getDataQuery];
self.data = s; // Doesn't work
[data release];
I have not used a custom setter. What is going on?
I totally forgot about this. Too much work. So I am not sure what the exact nature of the problem was. But I took a closer look at the retainCounts in the debugger (before trying out what Lou suggested).
It is a retain/release issue. I guess the rule is to be consistent with your usage. Anyway this works:
- (void) reloadFridgeData
{
MyDB * db = _GET_DB_CLASS;
if(self.data != nil)
{
self.data = nil;
}
NSMutableArray * newData = [db getData];
self.data = newData;
[newData release];
}
If data is definitely a property with no underlying value of the same name, then it must be accessed from self as in [[self data] release] or [self.data release]. However as mentioned if the property is set to auto-retain, you can accomplish the same thing by setting the property to a new value, such as nil. (As you've done in the code you noted works.)
Have you declared your data property to be "retain", as opposed to "assign"? You should have something like:
#property(retain) NSMutableArray *data;
If not, then when you assign to the data property, it won't increment the reference count. Your subsequent release will dealloc the object and the next reference to the data property will crash.
Take a look at the Apple docs for setter semantics for more information about retain.
Generally, if you are just using a retain property, you should always use self.data and call the generated setter.
It:
Calls release on the old value automatically (checks for nil)
Calls retain on the new value
Then your code would be simply:
self.data = s;
[s release];
Two things to do to debug:
Use static analysis -- I have personally found this tool to be 100% accurate with retain/release issues: http://clang.llvm.org/StaticAnalysisUsage.html
Follow the instructions on my blog post on memory debugging on the iPhone:
http://loufranco.com/blog/files/debugging-memory-iphone.html