Memory Management example - iphone

Here is some of the code from one of my classes, I was wondering am I handling the memory right or am I leaking anywhere?
#implementation CardViewController
#synthesize playerImage;
#synthesize cardLabel;
#synthesize card;
#synthesize frontView;
#synthesize backView;
#synthesize nameLabel;
#synthesize infoLabel;
#synthesize delegate;
-(void) initialiseButtons
{
NSLog(#"initialiseButtons %d",totalButtons);
int ypos = playerImage.frame.origin.y + playerImage.frame.size.height + 42;
for(int i=0; i<totalButtons; i++)
{
StatView *sv = [[StatView alloc] initWithYPos:ypos];
sv.tag = 100 + i;
[sv.overlayButton addTarget:self action:#selector(statTapped:)
forControlEvents:UIControlEventTouchUpInside];
sv.overlayButton.tag = 10 + i;
[self.frontView addSubview:sv];
ypos += 26;
}
}
-(IBAction) statTapped:(id) sender
{
UIButton *tappedButton = (UIButton *)sender;
int tag = tappedButton.tag;
if(delegate && [delegate respondsToSelector:#selector(cardTapped:)]) {
[delegate cardTapped:tag-10];
}
}
-(void) viewDidLoad
{
NSLog(#" viewDidLoad CVC");
[self initialiseButtons];
metaData = [[NSArray alloc]
initWithObjects:#"Height",#"Weight",#"Games",#"Attack",#"Defense", nil];
}
-(void) setCard:(Card *)newCard
{
NSLog(#"setCard");
[card release];
card = [newCard retain];
[self updateUI];
}
- (void)dealloc
{
[card autorelease];
[metaData autorelease];
[super dealloc];
}
#end
Where should I release StatView *sv = [[StatView alloc] initWithYPos:ypos];
If i released it every loop wouldnt that cause problems?
Besides that do I handle the rest of the memory ok?
Thanks
-Code

Yes, you should release that StatView at the end of each loop iteration, when you've inserted it into the view hierarchy.

You should try the analyzer built into XCode as it is very good at finding these type of memory leaks. Have a look.

Release the new StatView after this line
[self.frontView addSubview:sv];
[sv release]; // frontView retains sv
Release all properties declared as retain or copy in dealloc. Candidate properties: playerImage, cardLabel etc. Send a release message, not autorelease
\\[card autorelease];
[card release];
In viewDidUnload release all properties that are declared as IBOutlet and set the variable to nil
[frontView release], frontView = nil;

You should release it as soon as you've added it to the view hierarchy (by sending addSubview: with the newly-allocated view as an argument). This is because UIView objects retain their subviews.
One other problem I notice: your setCard method should first check whether the new and old cards are identical, and do nothing in that case. Otherwise, you may release your existing card, then try to retain it again, only to find that it has been dealloced.

Related

array retain in table view objective c

I have a small confusion. When I print my array in tableView dataSource method numberOfRowsInSection , my App crashes.
This is my code : in .h file
#interface AddColor : UIViewController<UITableViewDataSource,UITableViewDelegate>
{
UITableView *tblView;
NSArray *arrayColors;
}
#property(nonatomic,retain)NSArray *arrayColors;
#end
In .m file
#synthesize arrayColors;
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:NO];
arrayColors = [NSArray arrayWithObjects:#"blackColor", #"darkGrayColor", #"lightGrayColor", #"whiteColor", #"grayColor", #"redColor", #"greenColor", #"blueColor", #"cyanColor", #"yellowColor", #"magentaColor", #"orangeColor", #"purpleColor", #"brownColor", nil];
tblView=[[UITableView alloc]initWithFrame:CGRectMake(0, 0, 320, 460) style:UITableViewStylePlain];
tblView.delegate=self;
tblView.dataSource=self;
[self.view addSubview:tblView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
NSLog(#"%d",[arrayColors count]);//App crashes here.
return [arrayColors count];
}
My app crashes when I print [arrayColors count];
I find the solution for crash, I simply retain the array in viewDidLoad
[arrayColor retain];
and now working fine. but why my app crashes before when I print the [arrayColor count];
+ [NSArray arrayWithObjects:]
creates an autoreleased array - it's unsepcified when it is released, but most likely it's released when the viewDidLoad method returns, so it's an invalid (garbage) pointer that you access in [arrayColor count].
By retaining the array, you get rid of the deallocation error, but now you're leaking memory. The general approach of solving this the right way is to allocate and initialize the array in one of the initializer methods, like
- (id)init
{
if ((self = [super init])) {
arrayColors = [[NSArray alloc] initWithObjects:..., nil];
}
return self;
}
and then purge it in - dealloc in order not to leak memory:
- (void)dealloc
{
[arrayColors release];
[super dealloc];
}
More on the topic on Apple Developer.
Try using [self.arrayColors count] - use the ivar getters and setters to access the retained property
As an aside, I would strongly advise against hardcoding the size of your tableview. At the very least set the autoresizing mask.
You might've linked the DataSource and Delegate to your TableView instead of the File Owner.
if you do this,
[self setArrayColors:[NSArray arrayWithObjects:#"blackColor", #"darkGrayColor", #"lightGrayColor", #"whiteColor", #"grayColor", #"redColor", #"greenColor", #"blueColor", #"cyanColor", #"yellowColor", #"magentaColor", #"orangeColor", #"purpleColor", #"brownColor", nil]];
it should fix the problem without requiring another retain, but make sure you release it in the dealloc.
[arrayColors release];

iPhone Application Error Problem

Bear with me on this one.
I have an iphone application. It is a questionnaire application. There are several types of question, some have a slider, some have text input etc. I have developed a view controller for each type of question.
Two example types of question controllers are: TextInputQuestionViewController and SliderQuestionViewController.
I have a rootViewcontroller named QuestionnaireViewController. This is defined as follows:
#import <UIKit/UIKit.h>
#import "JSONKit.h";
#import "dbConnector.h"
#import "SliderQuestionViewController.h";
#import "TextInputQuestionViewController.h";
#import "MainMenuProtocol.h";
#interface QuestionnaireViewController : UIViewController {
NSDictionary* questions;
NSMutableArray* questionArray;
NSMutableArray* answerArray;
dbConnector* db;
SliderQuestionViewController* currQ; //need to create a generic var
TextInputQuestionViewController* currQ;
NSInteger currQNum;
NSString* qaTitle;
NSString* secId;
id<MainMenuProtocol>delegate;
}
#property(nonatomic, retain) NSDictionary* questions;
#property(nonatomic, retain) NSMutableArray* questionArray;
#property(nonatomic, retain) NSMutableArray* answerArray;
#property(nonatomic, retain) dbConnector* db;
#property(nonatomic, retain) SliderQuestionViewController* currQ;
#property(nonatomic, retain) TextInputQuestionViewController* currTI;
#property(nonatomic) NSInteger currQNum;
#property(nonatomic, retain) NSString* qaTitle;
#property(nonatomic, retain) NSString* secId;
#property(nonatomic, retain) id <MainMenuProtocol> delegate;
-(void) setQuestions;
-(void) startQuestion:(NSInteger)index isLast:(BOOL)last;
-(void) loadQuestions;
-(void) initialise;
-(void) finishQuestionnaire:(id)sender;
-(void) switchViews:(id)sender;
#end
#import "QuestionnaireViewController.h"
#import "dbConnector.h"
#import "ASIHTTPRequest.h"
#import "JSONKit.h";
#import "Answer.h";
#implementation QuestionnaireViewController
#synthesize questions, questionArray, db, currQ, currQNum, answerArray, qaTitle, secId, delegate;
-(void)viewDidLoad{
[self initialise];
answerArray = [[NSMutableArray alloc]init];
[super viewDidLoad];
self.title = qaTitle; //set to whatever section is
}
-(void) initialise {
currQNum = 0;
[self loadQuestions];
UIBarButtonItem *anotherButton = [[UIBarButtonItem alloc] initWithTitle:#"Start" style:UIBarButtonItemStylePlain target:self action:#selector(switchViews:)];
self.navigationItem.rightBarButtonItem = anotherButton;
}
-(void) loadQuestions {
db = [[dbConnector alloc]init];
//code to initialise view
[db getQuestions:secId from:#"http://dev.speechlink.co.uk/David/get_questions.php" respondToDelegate:self];
}
//called when questions finished loading
//stores dictionary of questions
- (void)requestFinished:(ASIHTTPRequest *)request
{
NSData *responseData = [request responseData];
NSString *json = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSDictionary *qs = [json objectFromJSONString];
self.questions = qs;
[json release];
[qs release];
[self setQuestions];
}
//assigns JSON to question objects
-(void) setQuestions {
questionArray = [[NSMutableArray alloc] init];
for (NSDictionary *q in self.questions) {
/* Create Question object and populate it */
id question;
if([[q objectForKey:#"type"] isEqualToString:#"Slider"]){
question = [[SliderQuestionViewController alloc]init];
//set min max values
}else if([[q objectForKey:#"type"] isEqualToString:#"Option"]){
}else if([[q objectForKey:#"type"] isEqualToString:#"TextInput"]){
question = [[TextInputQuestionViewController alloc]init];
}else if([[q objectForKey:#"type"] isEqualToString:#"ImagePicker"]){
}else{
//comments
}
//if else to create appropriate view controller - NEED to identify question type
[question setQuestionId:[q objectForKey:#"questionId"] withTitle:[q objectForKey:#"question"] number:[q objectForKey:#"questionNumber"] section:[q objectForKey:#"sectionId"] questionType: [q objectForKey:#"type"]];
/* Add it to question (mutable) array */
[questionArray addObject:question];
[question release];
}
}
-(void) startQuestion:(NSInteger)index isLast:(BOOL)last{
//currQ = [[QuestionViewController alloc]init];
currQ = [questionArray objectAtIndex:index];
//push currQ onto navigationcontroller stack
[self.navigationController pushViewController:currQ animated:YES];
[currQ addButton:self isLast: last];
}
//pushes new view onto navigation controller stack
-(void) switchViews:(id)sender{
Answer* ans = currQ.question.answer;
ans.questionId = currQ.question.qId;
ans.entryId = #"1";//temporary;
if(currQNum < [questionArray count] - 1){
if(currQNum > 0){
//if else for different input types
NSString* qt = currQ.question.qType;
if([qt isEqualToString:#"Slider"]){
ans.answer = currQ.sliderLabel.text;
}else if([qt isEqualToString:#"Option"]){
}else if([qt isEqualToString:#"TextInput"]){
//NSLog(#"%#", currQ.inputAnswer);
ans.answer = currQ.inputAnswer.text;
}else if([qt isEqualToString:#"ImagePicker"]){
}else{
}
[answerArray addObject: ans];
[ans release];
}
[self startQuestion:currQNum isLast:FALSE];
currQNum++;
}else{
ans.answer = currQ.sliderLabel.text;
[answerArray addObject: ans];
//store data temporarily - section finished
[self startQuestion:currQNum isLast:TRUE];
currQNum++;
}
[ans release];
}
-(void) finishQuestionnaire:(id)sender{
//go back to main manual
//if else statement for answers
NSString* answ = currQ.sliderLabel.text;
[answerArray addObject: answ];
[delegate finishedSection:answerArray section:secId];
[answ release];
[self.navigationController popToRootViewControllerAnimated:YES];
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
self.questions = nil;
self.currQ = nil;
[super viewDidUnload];
}
//hide back button in navigation bar
- (void) viewWillAppear:(BOOL)animated{
self.navigationItem.hidesBackButton = YES;
}
- (void)dealloc {
[currQ release];
[db release];
[questionArray release];
[questions release];
[super dealloc];
}
#end
the problematic lines with the above are in the switchViews function. I need to make the answer equal to the specific input component in that question view (slider value, text input value). So I need to make currQ a type that can be instantiated using any view controller.
I therefore need a generic variable to hold the current question. currQ holds the current question, but at the moment is of type SliderQuestionViewController. I tried to change this to id, but it throws a load of "Request For member...not a structure of union" and also a load of misassigned pointer issues.
Let me know if you need more code.
This reads like you want a pointer for a UIViewController, so just use that as the type. Then you can cast it down to whatever subclass you like later. For example:
-(void)myAction:(UIViewController *)vc {
SpecialViewController *svc = (SpecialViewController *)vc;
...
}
In your case, declare
UIViewController* currQ;
and then cast it as needed in the implementation to access the different properties and methods of your two subclasses.
If you're looking for a 'generic' variable, you should be able to use id. Make sure you don't define the type as id*, the asterisk should not be present.
A better idea, though, is to create a superclass for your question viewcontrollers. Create a superclass called QuestionViewController that inherits from UIViewController and have the Slider and TextInput (and any others) inherit from QuestionViewController. Then you can define your variable as: QuestionViewController* currQ; You can put any common functionality in that superclass as well and eliminate duplication.

Objective-C Memory management: ViewController in a ViewController that's inside a UITabBarController

I have a UITabBarController with three ViewControllers (A, B, and C). In ViewControllerB I have a UIScrollView. The UIScrollView consists of several instances of my PhotoViewController. These PhotoViewController objects are called from ViewControllerA, not ViewController B, where they're located.
The PhotoViewController instances have a UIImage and a two buttons. And at first, when I clicked on a button in one of my PhotoViewController instances I received a 'Thread 1: Program received signal: "EXC_BAD_ACCESS"' error. Looking around on stackoverflow that error seems to appear whenever there are memory management issues.
Since I was creating the PhotoViewController objects in a loop from a method called in ViewControllerA, and releasing those objects, I figured that by the time I switched over to ViewControllerB they were already released - and hence the memory issue.
But that's just my guess. Could you tell me if I should just stop releasing the PhotoViewController objects inside of the loop code? Because that's what I did (just commented that line out) and the program "works" fine. However, I'm still not sure if this is the proper way to handle it and if it is causing unknown memory management issues.
Here is some of my code:
ViewControllerA.m
//Creating an album in ViewControllerB, the photos in the album are PhotoViewController objects
-(IBAction)showAlbum:(UIButton *)sender
{
//Go goes here to get an album and display it in the UIScrollView
albumID = #"ALBUM_ID";
NSString* graphUrl = [NSString stringWithFormat:#"%#/photos?limit=10", albumID];
[_facebook requestWithGraphPath:graphUrl andDelegate:self];
}
...
- (void)request:(FBRequest *)request didLoad:(id)result {
//Code for array of photos
NSLog(#"%#",result);
NSString *requestType = [request.url stringByReplacingOccurrencesOfString:#"https://graph.facebook.com/" withString:#""];
if ([requestType isEqualToString:[NSString stringWithFormat:#"%#/photos?limit=10", albumID]]){
NSArray *photoAlbumArray=(NSArray*)[result valueForKey:#"data"];
[self.label setText:[NSString stringWithFormat:#"%i", [photoAlbumArray count]]];
for(UIViewController *controller in self.tabBarController.viewControllers)
{
if([controller isKindOfClass:[ViewControllerB class]])
{
ViewControllerB *mtbvc = (ViewControllerB *)controller;
[mtbvc setArray:photoAlbumArray];
self.tabBarController.selectedIndex = 1;//switch over to the second view to see if it worked
}
}
}
...
#end
ViewControllerB.m
//loop where I create PhotoViewController objects
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
arrayCount = [array count];
scroller.delegate=self;
scroller.pagingEnabled=YES;
scroller.directionalLockEnabled=YES;
scroller.showsHorizontalScrollIndicator=NO;
scroller.showsVerticalScrollIndicator=NO;
//should have an array of photo objects and the number of objects, correct?
scrollWidth = 0;
scroller.contentSize=CGSizeMake(arrayCount*scroller.frame.size.width, scroller.frame.size.height);
for (int i = 0; i < arrayCount;i++) {
PhotoViewController *pvc = [[PhotoViewController alloc] initWithNibName:#"PhotoViewController" bundle:nil];
UIImageView *scrollImageView = [[UIImageView alloc] initWithFrame:CGRectOffset(scroller.bounds, scrollWidth, 0)];
CGRect rect = scrollImageView.frame;
pvc.view.frame = rect;
[pvc view];
pvc.label.textColor = [UIColor whiteColor];
id individualPhoto = [array objectAtIndex:i];
NSLog(#"%#",individualPhoto);
NSArray *keys=[individualPhoto allKeys];
NSLog(#"%#",keys);
NSString *imageURL=[individualPhoto objectForKey:#"source"];
//here you can use this imageURL to get image-data and display it in imageView
NSURL *url = [NSURL URLWithString:imageURL];
NSData *data = [NSData dataWithContentsOfURL:url];
pvc.imageView.image = [[UIImage alloc] initWithData:data];
pvc.label.text = [individualPhoto objectForKey:#"id"];
//check to make sure the proper URL was passed
//I have an imageView next to the UIScrollView to test whether that works - it does.
[scroller addSubview:pvc.view];
[scrollImageView release];
//[pvc release];
scrollWidth += scroller.frame.size.width;
}
if (arrayCount > 3) {
pageControl.numberOfPages=3;
} else {
pageControl.numberOfPages=arrayCount;
}
pageControl.currentPage=0;
//[self.view addSubview:scroller];
}
PhotoViewController.m
#import "PhotoViewController.h"
#implementation PhotoViewController
#synthesize label, imageView;
-(IBAction)likeButton:(UIButton *)sender
{
//code goes here
for(UIViewController *controller in self.tabBarController.viewControllers)
{
if([controller isKindOfClass:[DemoAppViewController class]])
{
DemoAppViewController *davc = (DemoAppViewController *)controller;
[davc likePicture:self.label.text];
}
}
self.tabBarController.selectedIndex = 0;//switch over to the third view to see if it worked
}
-(IBAction)skipButton:(UIButton *)sender
{
//code goes here
}
-(IBAction)likeCommentButton:(UIButton *)sender
{
//code goes here
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
PhotoViewController.h
#import <UIKit/UIKit.h>
#import "DemoAppViewController.h"
#import "MyTabBarViewController.h"
#interface PhotoViewController : UIViewController {
IBOutlet UILabel *label;
IBOutlet UIImageView *imageView;
UIButton *likeButton;
UIButton *skipButton;
UIButton *likeCommentButton;
}
#property (nonatomic, retain) UILabel *label;
#property (nonatomic, retain) UIImageView *imageView;
-(IBAction)likeButton:(UIButton *)sender;
-(IBAction)skipButton:(UIButton *)sender;
-(IBAction)likeCommentButton:(UIButton *)sender;
#end
To write iOS apps, it is critical that you understand the memory management rules.
In ViewControllerB, viewDidLoad, you alloc the pvc.
Further down, you add the pvc's view as a subview of the scroller. This retains the pvc's view, but not the pvc itself. Then when you release the pvc, it's retain count is zero, and when you reference it later, it's gone. Crash. It seems like you need to pass in and retain a reference to the pvc in the controller that's using it.
I am not sure why you are using PhotoViewController(Subclassing UIViewController) instead of PhotoView(subclassing UIView). As you are not using any of the facility of the viewcontroller(no life cycle method and other).
If you subclass PhotoViewController with UIView and remove the viewcontroller's methods it will work and will not cause any memory issues as you have aleready discussed in your discussion with Rayfleck. ( it will be retained by parent view controller.)
If you are thinking about the events, then these will also be handled by view itself. But if you want to handle it in your controller then you can easily delegate, or pass your controller reference and invoke the event on it.
Thanks,

iphone - trying to understand #property

Suppose I have two classes. In the first one I declare this in Class1.h
#interface Class1 : UIViewController {
NSString *myString;
id myObject;
}
On the second class I go beyond that I declare it like
#interface Class2 : UIViewController {
NSString *myString;
id myObject;
}
#property (nonatomic, retain) NSString *myString;
#property (nonatomic, retain) id myObject;
and then I #synthesize myString, myObject on Class2.m
Then, on my main program, I create two objects: one based on Class1 and another one based on Class2.
What effect the #property of class2 will have? Will every value assigned to both values on Class2 be always retained? If so, do I need to "release" them? How?
Thanks.
Please read Declared Properties section of The Objective-C programming language
for a full explanation on properties ;)
In Class2:
In this case you set retain attribute to your property it is supposed to be retained in the implementation. This is done automatically when you synthesize a property.
This means that you should have
- (void) dealloc{
[myString release];
[myObject release];
[super dealloc];
}
and everything should be fine
In Class1, you don't have properties so myString and myObject is not visible from outside. But this does not mean that you shouldn't release them. It depends on the way you initialize them and/or if you send retain messages to them.
BTW, if you set assign a property you don't release it, just set it to nil in the dealloc method. If you set copy to it then you must release it.
EDIT
You said: *But suppose I have this *
#property (nonatomic, retain) UIView *myView;
and
myView = [[UIView alloc] initWithFrame:myFrame];
[self.view addSubview:myView];
[myView release];
? I am already releasing myView... do I have to release it again???
First, since you have your property defined that way, you should have dealloc method as:
- (void) dealloc{
[myView release];
[super dealloc];
}
So, the answer is NO you should not release it but actually is not correct.
Please take a look:
myView = [[UIView alloc] initWithFrame:myFrame]; //myView retainCount is 1
[self.view addSubview:myView]; //retainCount is 2
[myView release]; //retainCount is 1 again
later in dealloc method
- (void) dealloc{
[myView release]; // retainCount becomes 0, is deallocated
[super dealloc]; // subviews of self.view are released but myView was already deallocated!, so you have over released myView once ;(
}
This is the correct way: (Use your properties ;) )
UIView *aView = [[UIView alloc] initWithFrame:myFrame]; // init, retainCount is 1
self.myView = aView; // retainCount becomes 2
[aView release]; // retainCount becomes 1 again and we are fine.
[self.view addSubview:self.myView]; //retainCounts becomes 2 again.
even if it is 2 there is no problem because when self.view is deallocated its subviews also will be released. Hence self.myView retainCount will become 1 again later when self is deallocated.
- (void) dealloc{
[myView release]; //retainCounts becomes 1
[super dealloc]; // all its subviews of self.view are released hence myView retaincount becomes 1 and is released corretly
}
What is the difference?
Suppose self.myView is also retained by other object X and with the former approach, X's view will be pointing to an invalid object, because it was already released.
Hope it helps
EDIT2
As bbum's indication, this is a mini-mini-short tutorial on properties:
when you have
#property (... retain) NSObject *retainVar;
#property (... assign) NSObject *assignVar;
#property (... copy) NSObject *copyVar;
and you #synthesize them
is like having the following setters:
// retain
-(void)setRetainVar:(NSObject *)var {
if (retainVar != var) {
[retainVar release];
retainVar = [var retain];
}
}
//assign
-(void)setAssignVar:(NSObject *)var {
assignVar = var;
}
//copy
-(void)setCopyVar:(NSObject *)var {
if (copyVar != var) {
[copyVar release];
copyVar = [var copy];
}
}
(this means that if you assign directly an object you have to make sure is something equivalent to above setters, from the memory management point of view)
and your dealloc method should be something like:
- (void) dealloc{
[retainVar release];
assignVar = nil;
[copyVar release];
[super dealloc];
}
When setting your ivars
for example, inside of init:
- (id) init{
if ((self = [super init])){
//this is ok
retainVar = [[NSObject alloc] init];//but is retainVar was not nil we will have a leak ;(
//This is better
NSObject *obj = [NSObject alloc] init];
self.retainVar = obj;
[obj release];
//this is BAD
assignVar = [[NSObject alloc] init];//because this is like retaining it, later it will leak
//below is correct
NSObject *obj = [[[NSObject alloc] init] autorelease];
assignVar = obj;
//copy is pretty much like retain,
//this is ok
copyVar = [[NSObject alloc] init]; //but, if copyVar was not nil is a leak!
//below is better
NSObject *obj = [NSObject alloc] init]:
self.retainVar = obj;
[obj release];
}
return self;
}
Apple's "Learning Objective C - A Primer" tells you about that and more:
http://developer.apple.com/library/ios/#referencelibrary/GettingStarted/Learning_Objective-C_A_Primer/

UIView not retaining subviews

I made this play/pause/stop control but it doesn't work as expected. at load time it creates three views and stores them in an array, each view represents one state of the control. In the stopped state it contains a play button that is a member of a simple class cluster I made. In the other two states it contains a UIView with two of the buttons as subviews. In the first state it works and does exactly what it's supposed to, but when it tries to go to the next state it looks in the array and finds views at the playing state and paused state positions with no subviews. In fact, if you trace it through the execution of the loadView function the array never gets a view with subviews in it even though I called addSubview:(UIView *)view which the documentation says this about: This method retains view and sets its next responder to the receiver, which is its new superview.
I would really like some help trying to understand why this is happening. To be more clear, why don't the UIViews that are passed to the array have subviews when the local variables for them do.
Thanks in advance,
Rich
Here's the source:
// IMSpeechControl.h
#import <UIKit/UIKit.h>
#import "IMSpeechEngine.h"
typedef enum {
IMSpeechControlStateStopped = 0,
IMSpeechControlStatePlaying = 1,
IMSpeechControlStatePaused = 2
} IMSpeechControlState;
/* State Stopped: speech control should show a Play button.
State Playing: speech control should show a Pause button and a Stop button.
State Paused : speech control should show a Play button and a Stop button.
*/
#class IMSpeechControl;
#protocol IMSpeechControlDelegate <NSObject>
- (NSString *)speechControlNeedsText:(IMSpeechControl *)sender;
#end
#interface IMSpeechControl : UIViewController {
IMSpeechControlState controlState;
id delegate;
IMSpeechEngine *speechEngine;
NSMutableArray *controlButtons_;
CGRect frame_;
}
#property (nonatomic, readonly) IMSpeechControlState controlState;
#property (nonatomic, assign) id<IMSpeechControlDelegate> delegate;
// Designated initilazer
- (IMSpeechControl *)initWithFrame:(CGRect)frame;
// This must be here, do not call it from outside it's control buttons
- (void)changeToState:(IMSpeechControlState)controlState;
- (void)play;
- (void)pause;
- (void)stop;
#end
This is the important one.
// IMSpeechControl.m
#import "IMSpeechControl.h"
#import "IMSpeechControlButton.h"
#interface IMSpeechControl ()
#property (nonatomic, assign) IMSpeechEngine *speechEngine;
#property (nonatomic, retain) NSMutableArray *controlButtons;
// Used only for initialization, do not change after calling initWithFrame
// to change the view size after creation
#property (nonatomic) CGRect frame;
- (void)speechEngineDidFinishSpeaking:(NSNotification *)notifictation;
#end
#implementation IMSpeechControl
#synthesize controlState, delegate, speechEngine, frame=frame_;
#synthesize controlButtons=controlButtons_;
/* State Stopped: speech control should show a Play button.
State Playing: speech control should show a Pause button and a Stop button.
State Paused : speech control should show a Play button and a Stop button.
*/
- (IMSpeechControl *)initWithFrame:(CGRect)aFrame {
if (self = [super init]) {
self.frame = aFrame;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(speechEngineDidFinishSpeaking:) name:kDidFinishSpeakingNotificationName object:self.speechEngine];
}
return self;
}
- (void)loadView {
// Initialization code.
// Create the main view.
UIView *aView = [[UIView alloc] initWithFrame:self.frame];
self.view = aView;
[aView release];
// Create the sub-views and store them in an array.
NSMutableArray *controls = [[NSMutableArray alloc] initWithCapacity:3];
// The stopped state play button view can be used directly since it is the only button shown.
IMSpeechControlButton *button = [[IMSpeechControlButton alloc] initWithFrame:self.frame forControl:self style:IMSpeechControlButtonStylePlay];
[controls insertObject:button atIndex:(NSUInteger)IMSpeechControlStateStopped];
[button release];
// The other two states require two buttons each, so the two buttons must be grouped into a UIView that can be easily switched out.
// Make two frames, one for the left and one for the right. Both are half the width of the main view
// The one on the left has the same origin as the main view...
CGRect halfFrameLeft = CGRectMake(frame_.origin.x, frame_.origin.y, frame_.size.width / 2, frame_.size.height);
// and the one on the right starts half-way across the main view
CGRect halfFrameRight = CGRectMake((frame_.origin.x + (frame_.size.width / 2)), frame_.origin.y, frame_.size.width / 2, frame_.size.height);
// Playing state
// Pause button
UIView *playingState = [[UIView alloc] initWithFrame:self.frame];
IMSpeechControlButton *plsPauseButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameLeft forControl:self style:IMSpeechControlButtonStylePause];
[playingState addSubview:plsPauseButton];
[plsPauseButton release];
// Stop button
IMSpeechControlButton *plsStopButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameRight forControl:self style:IMSpeechControlButtonStyleStop];
[playingState addSubview:plsStopButton];
[plsStopButton release];
[controls insertObject:playingState atIndex:(NSUInteger)IMSpeechControlStatePlaying];
[playingState release];
// Paused state
// Play button
UIView *pausedState = [[UIView alloc] initWithFrame:self.frame];
IMSpeechControlButton *pasPlayButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameLeft forControl:self style:IMSpeechControlButtonStylePlay];
[pausedState addSubview:pasPlayButton];
[pasPlayButton release];
// Stop button
IMSpeechControlButton *pasStopButton = [[IMSpeechControlButton alloc] initWithFrame:halfFrameRight forControl:self style:IMSpeechControlButtonStyleStop];
[pausedState addSubview:pasStopButton];
[pasStopButton release];
[controls insertObject:pausedState atIndex:(NSUInteger)IMSpeechControlStatePaused];
[pausedState release];
// store the array in an instance variable
self.controlButtons = controls;
[controls release];
// Set the view to it's first state (stopped)
IMSpeechControlButton *stoppedState = (IMSpeechControlButton *)[self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStateStopped];
[self.view addSubview:stoppedState];
controlState = IMSpeechControlStateStopped;
}
- (IMSpeechEngine *)speechEngine {
if (nil == speechEngine) {
self.speechEngine = [IMSpeechEngine sharedManager];
}
return speechEngine;
}
- (void)changeToState:(IMSpeechControlState)state {
// This line caused my problem
// IMSpeechControlButton *currentView = [[self.view subviews] objectAtIndex:0];
// It should look like this
UIView *currentView = [[self.view subviews] objectAtIndex:0];
switch (state) {
case IMSpeechControlStateStopped:
{
UIView *stoppedState = (UIView *)[self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStateStopped];
[self.view addSubview:stoppedState];
[UIView animateWithDuration:0.15
animations:^{
currentView.alpha = 0.5;
stoppedState.alpha = 0.15;
}
completion:^(BOOL finished)
currentView.alpha = 0.0;
[currentView removeFromSuperview];
[UIView animateWithDuration:0.15 animations:^{stoppedState.alpha = 0.5;}];
}];
controlState = IMSpeechControlStateStopped;
break;
}
case IMSpeechControlStatePlaying:
{
UIView *playingState = [self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStatePlaying];
[self.view addSubview:playingState];
[UIView animateWithDuration:0.15
animations:^{
currentView.alpha = 0.5;
playingState.alpha = 0.15;
}
completion:^(BOOL finished){
currentView.alpha = 0.0;
[currentView removeFromSuperview];
[UIView animateWithDuration:0.15 animations:^{playingState.alpha = 0.5;}];
}];
controlState = IMSpeechControlStatePlaying;
break;
}
case IMSpeechControlStatePaused:
{
UIView *pausedState = (UIView *)[self.controlButtons objectAtIndex:(NSUInteger)IMSpeechControlStatePaused];
[self.view addSubview:pausedState];
[UIView animateWithDuration:0.15
animations:^{
currentView.alpha = 0.5;
pausedState.alpha = 0.15;
}
completion:^(BOOL finished){
currentView.alpha = 0.0;
[currentView removeFromSuperview];
[UIView animateWithDuration:0.15 animations:^{pausedState.alpha = 0.5;}];
}];
controlState = IMSpeechControlStatePaused;
break;
}
default:
NSLog(#"Error %lu is not a recognized IMSpeechControlState", state);
break;
}
}
- (void)speechEngineDidFinishSpeaking:(NSNotification *)notifictation {
// This notification is only sent if it has finished speaking and is therefore stopped.
[self changeToState:IMSpeechControlStateStopped];
}
- (void)play {
NSString *text = [delegate speechControlNeedsText:self];
[self.speechEngine speakText:text];
[self changeToState:IMSpeechControlStatePlaying];
}
- (void)pause {
[self.speechEngine pauseSpeaking];
[self changeToState:IMSpeechControlStatePaused];
}
- (void)stop {
[self.speechEngine stopSpeaking];
[self changeToState:IMSpeechControlStateStopped];
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self forKeyPath:kDidFinishSpeakingNotificationName];
[super dealloc];
}
#end
// IMSpeechControlButton.h
#import "IMSpeechControl.h"
typedef enum {
IMSpeechControlButtonStylePlay,
IMSpeechControlButtonStylePause,
IMSpeechControlButtonStyleStop
}IMSpeechControlButtonStyle;
#interface IMSpeechControlButton: UIView {
IMSpeechControl *speechControl;
UIImageView *imageView;
}
#property (nonatomic, assign) IMSpeechControl *speechControl;
#property (nonatomic, retain) UIImageView *imageView;
- (IMSpeechControlButton *)initWithFrame:(CGRect)aRect
forControl:(IMSpeechControl *)control
style:(IMSpeechControlButtonStyle)style;
#end
// IMSpeechControlButton.m
#import "IMSpeechControlButton.h"
#import <Foundation/NSObjCRuntime.h>
#implementation IMSpeechControlButton
#synthesize speechControl;
#synthesize imageView;
- (IMSpeechControlButton *)initWithFrame:(CGRect)aRect
forControl:(IMSpeechControl *)control
style:(IMSpeechControlButtonStyle)style
{
NSString *str;
switch (style) {
case IMSpeechControlButtonStylePlay:
str = #"IMSpeechControlPlay";
break;
case IMSpeechControlButtonStylePause:
str = #"IMSpeechControlPause";
break;
case IMSpeechControlButtonStyleStop:
str = #"IMSpeechControlStop";
break;
default:
break;
}
isa = NSClassFromString(str);
// the speechControl must be set before calling subclass implementation of initWithFrame
self.speechControl = control;
return [self initWithFrame:aRect];
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code.
}
*/
- (void)dealloc {
[super dealloc];
}
#end
All the control buttons have the exact same code as the play button, except that the handleGesture method calls the appropriate play/pause/stop function on the speechControl. The only reason I created all of them was so each of them could have their own images and so they can play different animations before changing state, but I didn't get to that yet.
// IMSpeechControlPlay.h
#import "IMSpeechControlButton.h"
#interface IMSpeechControlPlay : IMSpeechControlButton {
}
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer;
#end
// IMSpeechControlPlay.m
#import "IMSpeechControlPlay.h"
#implementation IMSpeechControlPlay
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code.
// TODO: set the image view
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleGesture:)];
gestureRecognizer.numberOfTapsRequired = 1;
gestureRecognizer.numberOfTouchesRequired = 1;
gestureRecognizer.delaysTouchesBegan = YES;
[self addGestureRecognizer:gestureRecognizer];
[gestureRecognizer release];
}
return self;
}
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
[speechControl play];
}
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
// Drawing code.
}
*/
- (void)dealloc {
[super dealloc];
}
#end
I found the problem, it was in this line:
IMSpeechControlButton *currentView = (IMSpeechControlButton *)[[self.view subviews] objectAtIndex:0]; in the changeState method.
I'd changed and moved this line several times during development, and had an older version of the file where it was correctly stated as:
UIView *currentView = [[self.view subviews] objectAtIndex:0];
but I just noticed that the file I was building has the first version. The version that I copied the source from turned out to be an old version that looked like this:
UIView *currentView = (IMSpeechControl *)[[self.view subviews] objectAtIndex:0];
Changing that to a UIView pointer makes it work. Looking at the debug info it looks like I was wrong about the subviews not getting retained, they were actually just unavailable in the debugger being cast away when the play control was pressed. Setting the speechControl variable before calling init actually works fine.
Thanks for all the advice and so quickly.
This method is just really wrong
- (IMSpeechControlButton *)initWithFrame:(CGRect)aRect
forControl:(IMSpeechControl *)control
style:(IMSpeechControlButtonStyle)style
Basically there is nothing to return here
self.speechControl = control;
return [self initWithFrame:aRect];
Because you haven't actually inited "self" yet. You need to rewrite that function to have a proper initializer like the one you have in IMSpeechControlPlay.