NSMutableArray elements not being released in dealloc - iphone

I have define a NSMutableArray in .h file
NSMutableArray *arrayBatLevel;
init it in .m file
- (void)viewDidLoad {
[super viewDidLoad];
[self DataTimer];
}
-(void)DataTimer
{
[recordDataTimer invalidate];
recordDataTimer = [NSTimer scheduledTimerWithTimeInterval:[timeInterval.text floatValue]
target:self
selector:#selector(recordData)
userInfo:nil
repeats:YES];
}
-(void)recordData
{
if ([aSwitch isOn] == YES) {
if (arrayBatLevel == nil) {
arrayBatLevel = [[NSMutableArray alloc] init];
NSLog(#"alloc arrayBatLevel");
}
[arrayBatLevel addObject:batLevel.text];
}
}
and release in dealloc
- (void)dealloc {
[arrayBatLevel release];
[super dealloc];
}
but, it seems it’s not releasing all the objects inside the NSMutableArray.
When I exit this app and run it gain, these objects still in NSMutableArray, why?

Your problem is not with the array, it's with the objects inside the array. You don't indicate where "batLevel" came from. I'm guessing that "batLevel" is retained somewhere else, and with it "batLevel.text".

Related

objective-c memory management clarification

I am trying to make a simple iphone app and I have been playing around with features and recently, delegates. I am just confused with regards to memory management because apparently "good code" makes my app crash with exc_bad_access.
I have an object with two data members and implementation empty for now.
#implementation semester: NSObject{
NSInteger ID;
NSString *name;
}
then my delegate method:
- (void) receiveSemester:(semester *)newSemester {
[test setText:newSemester.name];
}
and a view that is used as a form which has:
#interface addSemesterController : UIViewController {
id<ModalViewDelegate> delegate;
UITextField *txtName;
UILabel *prompt;
UIButton *ok;
UIButton *cancel;
}
all objects are made properties and synthesized in the application file. Here is the method that used the delegate:
- (IBAction) okClick:(id)sender{
// create semester object and return it
semester *created = [[semester alloc] init];
created.name = txtName.text;
[delegate receiveSemester:created];
[self dismissModalViewControllerAnimated:YES];
}
And my dealloc method looks like this:
- (void)dealloc {
/*
[txtName dealloc];
[prompt dealloc];
[ok dealloc];
[cancel dealloc];
*/
[super dealloc];
}
With the deallocs of the objects contained in the form commented out, my app runs ok. However, when I uncomment them, I receive the exc_bad_access error in my delegate protocol:
// in main view controller
- (void) receiveSemester:(semester *)newSemester {
[test setText:newSemester.name];
// test is a UILabel
}
I tried the zombie method and it says that the label calls a released object. I am not releasing my semester object in the "form" controller, and even if I was the delegate function is called before deallocating the view.
Clearly I should not be releasing the objects in the dealloc method, I am just unclear in the why I shouldn't.
Again, thanks in advance for the help.
Use release to release the variables instead of calling dealloc on variables, due to this you are having issue -
- (void)dealloc {
[txtName release];
[prompt release];
[ok release];
[cancel release];
[super dealloc];
}
try writing
[txtName release];
[prompt release];
[ok release];
[cancel release];
instead of dealloc and these objects will be deallocated properly

leak is raising in NSTimer

I am using a sub class in my application, in which i am using nstimer to detect user idle/inactivity.
Here is a leak and it raised on every tap.
here is code of my class
- (void)sendEvent:(UIEvent *)event {
[super sendEvent:event];
isRootView=FALSE;
// Only want to reset the timer on a Began touch or an Ended touch, to reduce the number of timer resets.
NSSet *allTouches = [event allTouches];
if ([allTouches count] > 0) {
// allTouches count only ever seems to be 1, so anyObject works here.
UITouchPhase phase = ((UITouch *)[allTouches anyObject]).phase;
if (phase == UITouchPhaseBegan)
[self resetIdleTimer];
}
}
- (void)resetIdleTimer {
if (idleTimer)
{
if ([idleTimer isValid])
{
[idleTimer invalidate];
//[idleTimer release];
//idleTimer=nil;
}
}
maxIdleTime = 60;
if (!isRootView)
{
idleTimer = [NSTimer scheduledTimerWithTimeInterval:maxIdleTime target:self selector:#selector(idleTimerExceeded) userInfo:nil repeats:NO];
[idleTimer retain];
}
else {
if ([idleTimer isValid])
{
[idleTimer invalidate];
//[idleTimer release];
//idleTimer = nil;
}
if ([resetTimer isValid]) {
[resetTimer invalidate];
resetTimer=nil;
}
}
}
- (void)idleTimerExceeded {
alert=[[UIAlertView alloc] initWithTitle:#"Confirmation!" message:#"Would you like to continue placing the order ?" delegate:self cancelButtonTitle:#"NO" otherButtonTitles:#"YES", nil];
alert.tag=100;
[alert show];
[alert release];
resetTimer=[NSTimer scheduledTimerWithTimeInterval:maxIdleTime target:self selector:#selector(resetApplication) userInfo:nil repeats:NO] ;
}
-(void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (alertView.tag==100 ) {
if (buttonIndex==1)
{
if ([resetTimer isValid]) {
[resetTimer invalidate];
resetTimer=nil;
}
}
else {
[self resetApplication];
}
}
}
-(void) resetApplication
{
isRootView=TRUE;
[alert dismissWithClickedButtonIndex:1 animated:YES];
if ([resetTimer isValid])
{
[resetTimer invalidate];
resetTimer=nil;
}
if (idleTimer)
{
[idleTimer invalidate];
[idleTimer release];
idleTimer = nil;
}
SushiTeriaAppDelegate *appDelegate=(SushiTeriaAppDelegate*)[[UIApplication sharedApplication] delegate];
[appDelegate resetApp];
}
- (void)dealloc {
[super dealloc];
//[resetTimer release];
[alert release];
}
I have retain this timer. If dont retain then application get crashed.
Please guide me how to remove this leak
shivam
Use assigned property for timer variable.
ex.
NSTimer globalTimer;
#property (nonatomic, assigned) NSTimer globalTimer;
#synthesize globalTimer;
Now assign timer on this variable and use like self.globalTimer.
One thing is that always assign nil on variable after use. Do not release it another wise it will
crashed your application.
What is happening is, you are calling the resetIdleTimer method every time the user taps on the screen. This increases the retain count of the idleTimer every time because you have retained it. The reason your app crashes if you don't retain is because you get an autoreleased NSTimer object from NSTimer class method
idleTimer=[NSTimer scheduledTimerWithTimeInterval:maxIdleTime target:self selector:#selector(idleTimerExceeded) userInfo:nil repeats:NO];
This means that you cannot use this object outside that particular method as you do in your resetApplication method. Now you could check the retain count every time you retain it but that is a hassle. So what I suggest is you declare idleTimer as a retain property.
#property (retain) NSTimer *idleTimer;
and synthesize its setters and getters after implementation.
#synthesize idleTimer;
now use
self.idleTimer=[NSTimer scheduledTimerWithTimeInterval:maxIdleTime target:self selector:#selector(idleTimerExceeded) userInfo:nil repeats:NO];
and release idleTimer only in your dealloc.

viewDidUnload - stopping a method when the view is changed

I'm looking to stop an audio file from playing when the view is changed.
I'm using a tabController and I would like the audio that's playing to stop when the user moves to a different view. I'm not sure where and how I would do this. in the viewDidUnload perhaps?
here's the method I'm using to play the audio file:
-(void)startPlaying {
[NSTimer scheduledTimerWithTimeInterval:15 target:self selector:#selector(startPlaying) userInfo:nil repeats:NO];
NSString *audioSoundPath = [[ NSBundle mainBundle] pathForResource:#"audio_file" ofType:#"caf"];
CFURLRef audioURL = (CFURLRef) [NSURL fileURLWithPath:audioSoundPath];
AudioServicesCreateSystemSoundID(audioURL, &audioID);
AudioServicesPlaySystemSound(audioID);
}
thanks for any help
Something like this in your view controller (untested):
- (void)viewDidLoad
{
[super viewDidLoad];
// Load sample
NSString *audioSoundPath = [[NSBundle mainBundle] pathForResource:#"audio_file"
ofType:#"caf"];
CFURLRef audioURL = (CFURLRef)[NSURL fileURLWithPath:audioSoundPath];
AudioServicesCreateSystemSoundID(audioURL, &audioID)
}
- (void)viewDidUnload
{
// Dispose sample when view is unloaded
AudioServicesDisposeSystemSoundID(audioID);
[super viewDidUnload];
}
// Lets play when view is visible (could also be changed to viewWillAppear:)
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self startPlaying];
}
// Stop audio when view is gone (could also be changed to viewDidDisappear:)
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if([self.audioTimer isValid]) {
[self.audioTimer invalidate];
}
self.timer = nil;
}
// Start playing sound and reschedule in 15 seconds.
-(void)startPlaying
{
self.audioTimer = [NSTimer scheduledTimerWithTimeInterval:15 target:self
selector:#selector(startPlaying)
userInfo:nil
repeats:NO];
AudioServicesPlaySystemSound(audioID);
}
Missing:
Error checking
Property or ivar for audioTimer.
Could also keep the same timer, with repeats YES.
Freeing resources in dealloc.
Testing

Initializing view controller referenced from application launch

My data is stored in a member variable (NSArray) of a view controller. The problem I'm running into is that my data is loaded from a database on application launch, but the NSArray isn't initialized until later, so the addObject calls silently fail.
I've tried putting breakpoints on the init, initWithNibName, viewWillAppear, and viewDidLoad methods of my view controller (SafeTableViewController), but none of them catch before the addObject call. I assume that the actual view controller is initialized, because when I watch it in the debugger it has a nonzero address, but the NSArray has the address 0x0 when addObject is called.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
databaseName = #"DubbleDatabase.sql";
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [documentPaths objectAtIndex:0];
databasePath = [documentsDir stringByAppendingPathComponent:databaseName];
[self checkAndCreateDatabase];
[self readSafeItemsFromDatabase ];
// Add the tab bar controller's current view as a subview of the window
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
return YES;
}
- (void) readSafeItemsFromDatabase {
// some code skipped here, but basically: open sqlite3 database, iterate through rows
while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
// read database, get data fields out
SafeItem *safeItem = [[SafeItem alloc] initWithName:aName price:aPrice category:aCategory];
[safeTableViewController addItemToSafe: safeItem]; // PROBLEM HERE
[safeItem release];
}
}
sqlite3_close(database);
}
In SafeTableViewController.m:
- (void) addItemToSafe : (SafeItem*) newSafeItem {
[self.safeItems addObject: newSafeItem];
}
// I put a breakpoint on this, but it does not hit. i.e. safeItems is not initialized when addObject is called on it.
-(id) init {
if(self = [super initWithNibName:#"SafeTableViewController" bundle:nil]){
self.safeItems = [[NSMutableArray alloc] init];
}
return self;
}
EDIT: Thought of a way to fix this problem. Still curious though: when is init and/or initWithNibName called? Here's the proposed solution:
- (void) addItemToSafe : (SafeItem*) newSafeItem {
if(self.safeItems == nil){
self.safeItems = [[NSMutableArray alloc] init];
}
[self.safeItems addObject: newSafeItem];
}
How do you setup your instance of SafeTableViewController? By code? By nib?
-(id) init is not the designated initializer.
You probably want to use
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle {
if(self = [super initWithNibName:nibName bundle:nibBundle]){
self.safeItems = [[NSMutableArray alloc] init];
}
return self;
}
or initialize elsewhere, i.e. in viewDidLoad.
The problem is that you shouldn't be storing your data in a view controller. Create a model object (SafeItemManager for instance) to hold your data and point the view controller at that.

Setting UIActivityIndicatorView while view is prepared

I have a UITabbBarController with a UITableView. Under certain circumstances the TabBarControllers dataset requires updating when a user arrives from another view,
e.g. the initial load when the TabBarController is called the first time, or when the settings are changed.
This dataset update takes about 2 seconds and I want to show an UIActivityIndicatorView.
Trouble is that when I enter from another view I don't know which view to attach it to, since the loading of the tabbarController is carried out in the viewWillAppear method.
Any clues how I can go about this?
I've done this sort of thing in the viewDidAppear method. My code kicks off a background task to load the data from a url. It also hands the background task a selector of a method to call on the controller when it is done. That way the controller is notified that the data has been downloaded and can refresh.
I don't know if this is the best way to do this, but so far it's working fine for me :-)
To give some more details, in addition to the selector of the method to call when the background task has loaded the data, I also and it a selector of a method on the controller which does the loading. That way the background task manages whats going on, but the view controller provides the data specific code.
Here's there viewDidAppear code:
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (reloadData) {
BackgroundTask *task = [[BackgroundTask alloc] initWithMethod:#selector(loadData) onObject:self];
task.superView = self.view.superview;
task.notifyWhenFinishedMethod = #selector(loadFinished);
[task start];
[task release];
}
}
The background task has an optional superView because it will add a new UIView to it containing an activity indicator.
BackgroundTask.m looks like this:
#implementation BackgroundTask
#synthesize superView;
#synthesize longRunningMethod;
#synthesize notifyWhenFinishedMethod;
#synthesize obj;
- (BackgroundTask *) initWithMethod:(SEL)aLongRunningMethod onObject:(id)aObj {
self = [super init];
if (self != nil) {
self.longRunningMethod = aLongRunningMethod;
self.obj = aObj;
}
return self;
}
- (void) start {
// Fire into the background.
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:#selector(execute:)object:nil];
thread.name = #"BackgroundTask thread";
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(taskFinished:) name:NSThreadWillExitNotification object:thread];
[thread start];
[thread release];
}
- (void) execute:(id)anObject {
// New thread = new pool.
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (self.superView != nil) {
busyIndicatorView = [[BusyIndicator alloc] initWithSuperview:self.superView];
[busyIndicatorView performSelectorOnMainThread:#selector(addToSuperView)withObject:nil waitUntilDone:YES];
}
// Do the work on this thread.
[self.obj performSelector:self.longRunningMethod];
if (self.superView != nil) {
[busyIndicatorView performSelectorOnMainThread:#selector(removeFromSuperView)withObject:nil waitUntilDone:YES];
}
[pool release];
}
- (void) taskFinished:(NSNotification *)notification {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSThreadWillExitNotification object:notification.object];
[self performSelectorOnMainThread:#selector(notifyObject)withObject:nil waitUntilDone:NO];
}
- (void) notifyObject {
// Tell the main thread we are done.
if (self.notifyWhenFinishedMethod != nil) {
[self.obj performSelectorOnMainThread:self.notifyWhenFinishedMethod withObject:nil waitUntilDone:NO];
}
}
- (void) dealloc {
self.notifyWhenFinishedMethod = nil;
self.superView = nil;
self.longRunningMethod = nil;
self.obj = nil;
[super dealloc];
}
#end
Finally as I said I put up a activity indicator. I have a xib which contains a 50% transparent blue background with an activity indicator in the middle. There is a controller for it which has this code:
#implementation BusyIndicator
#synthesize superView;
#synthesize busy;
- (BusyIndicator *) initWithSuperview:(UIView *)aSuperView {
self = [super initWithNibName:#"BusyIndicator" bundle:nil];
if (self != nil) {
self.superView = aSuperView;
}
return self;
}
- (void) addToSuperView {
// Adjust view size to match the superview.
[self.superView addSubview:self.view];
self.view.frame = CGRectMake(0,0, self.superView.frame.size.width, self.superView.frame.size.height);
//Set position of the indicator to the middle of the screen.
int top = (int)(self.view.frame.size.height - self.busy.frame.size.height) / 2;
self.busy.frame = CGRectMake(self.busy.frame.origin.x, top, self.busy.frame.size.width, self.busy.frame.size.height);
[self.busy startAnimating];
}
- (void) removeFromSuperView {
[self.busy stopAnimating];
[self.view removeFromSuperview];
}
- (void) dealloc {
self.superView = nil;
[super dealloc];
}
#end
Hoep this helps.