My question regards the use of activity indicator in an iPhone project.
I have a class that contains an UIActivityIndicatorView
#interface StatusView : UIView
{
UIActivityIndicatorView *indicator;
UILabel *textLabel;
}
- (id)initWithFrame:(CGRect)frame Text:(NSString*)text andShowIndicator:(BOOL)value;
In my business logic code I call [indicator startAnimating] and the frame appears at the bottom of the screen. The code also contains a dealloc method that releases the indicator
- (void)dealloc
{
[indicator release];
[super dealloc];
}
Most of the time the indicator works well, however there are a few occasions that never disappears.
Do I always need to explicitly call the stopAnimating method? Does the release handle it?
What is the proper usage?
stopAnimating: method stops the wheel of UIActivityIndicatorView and release release the object.In Objective-C each object has an internal counter that is used to keep track of all references used by the objects or object has. [object retain] increments the counter by 1 and [object release] decrements the counter by 1. When counter reaches to zero, dealloc is then called. release is about memory management while stopAnimating: is a functionality of UIActivityIndicatorView.
So if you want to stop animating your UIActivityIndicatorView you would have to call stopAnimating: method. In ARC dont have to do release so better to use ARC.
the best way when you are using this object is stopAnimating when you want to stop, remove from super view ( [activityObject removeFromsuperview] ) and finally release it. [ activityObject release];
Related
NB: To clarify what I'm trying to do here:
I have an instance of a subclass of UIView. When this instance gets released from a View Controller I would like that the dealloc method of that instance be called.
The point being to release other objects within that instance of the subclass of UIView.
In the View Controller:
- (void)viewDidLoad
{
[super viewDidLoad];
self.mav = [[MapAreaView alloc]init];
[self.view addSubview:self.mav];
[self.mav release];
t_ = [NSTimer scheduledTimerWithTimeInterval: 20.0f target: self selector:#selector(onTick) userInfo: nil repeats:NO];
}
and:
- (void)onTick
{
NSLog(#"Releasing...");
[t_ invalidate];
t_ = nil;
[self.mav release];
[self.mav release];
[self.mav release];
NSLog(#"Done releasing.");
}
In MapAreaView:
- (void)dealloc
{
NSLog(#"map view area dealloc called.");
[super dealloc];
}
I am trying to release the MapAreaView and have it's dealloc method called.
Also, when I run this app, Releasing... and Done releasing. get printed, but not map view area dealloc called. And despite all of my excessive release messages sent to self.mav the app doesn't crash. Weird.
Ideas?
EDIT 1
#interface MemoryManagmentViewController : UIViewController {
MapAreaView *mav_;
NSTimer *t_;
}
#property (nonatomic, retain) MapAreaView *mav;
which is then synthesized: #synthesize mav = mav_;
EDIT 2
Please not that the timer is not for use in my real application. I'm just using it to learn about memory management.
First off, don't use [self.mav release]. This is incorrect use of properties, and is not guaranteed to release your object. Use self.mav = nil instead.
Secondly, I presume your crazy timer is there just to try and force the release of your view and isn't really code you are using? Because it really shouldn't be there!
To manage the memory of a view that you want to both keep a pointer to and add as a subview to your main view, remove the release and timer messages from your viewDidLoad. You now have one retained pointer to mav, via your property, and the view will be retaining it as well since it is a subview.
In viewDidUnload, your view no longer exists so it will no longer have a pointer to mav. Therefore, if you put self.mav = nil there, mav will be released.
You shouldnt try to release it while it is still a subview of another view.
You don't get to choose when dealloc gets run*
You don't know what else in the iOS framework is retaining your map view.
You should think about memory management in terms of ownership - while you want an object, make sure you've retained it (either explicitly or as a property) and when you are finished with it, make sure you release it (etiher by calling release, autorelease or by setting your property to nil).
Your map view will get released eventually, don't try to force it!
(And it's been said in many other answers but it pretty important so here it is again - don't call self.property release, do self.property = nil; instead :)
*not entirely true for object in libraries you have written yourself but it's definitely true for objects from third party frameworks, including ones from Apple!
The line:
[self.view addSubview:self.mav];
will retain self.mav and should be paired by this line in onTick.
[self.mav removeFromSuperview];
I'm not sure why your over-releasing in onTick doesn't release self.mav anyway - but if you do weird stuff you can guarantee that weird stuff will happen.
Don't release it within onTick again. Do these two steps:
[self.mavview removeFromSuperView];
self.mavview = nil;
That's it. There won't be any more references, so it will be deallocated.
First and foremost thing is
NEVER call release on self.iVar
Donot user self.iVar while allocating.
These are the basic things in memory management.
For the code below if I nillify the indicator by "self" it crashes if dont use self than no crash.
Isnt it the rule that I should always use "self" to access ivars created and retained by #property ?
#property(nonatomic,retain) UIActivityIndicatorView* activityIndicator;
if(activityIndicator!=nil){
[activityIndicator removeFromSuperview];
//self.activityIndicator = nil; //crashes!
activityIndicator = nil; //does not crash
}
Generally speaking:
self.activityIndicator = nil; //crashes!
will release the activityIndicator, so this is possibly related to the crash.
activityIndicator = nil; //does not crash
will not release the activity indicator, you don't have a crash, you have a memory leak.
In your concrete case, possibly the crash depends on the fact that when you execute this:
[activityIndicator removeFromSuperview];
the activity indicator is released; now, if it also happens that retain count goes to 0, the object is also deallocated, but the property is not updated to reflect the fact that the object has been deallocated. SO, when you set it to nil, the setter tries to release it, but the obejct dos not exist anymore and hence the crash.
This is a guess. It should not happen if you retained correctly the activity indicator in your class. So, either you review the code where you create the activity indicator and the places in your code where you use activityIndicator or you post it for more help...
I'm relatively new to Objective-C and coding. I've tried doing a little dev on my own but have now become stuck on what is probably a rookie error.
I've created a tab bar controller with 5 views, one such view is a UIWebView. I've got the Webview working and it loads, but when I select a different tab, the app crashes. Please find my code below and any help would be appreciated!
#import <UIKit/UIKit.h>
#interface LiveViewController : UIViewController {
IBOutlet UIWebView *liveView;
}
#property (nonatomic, retain) UIWebView *liveView;
#end
#import "LiveViewController.h"
#implementation LiveViewController
#synthesize liveView;
// The designated initializer. Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
/*
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization.
}
return self;
}
*/
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
}
*/
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[self.liveView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.britishseapower.co.uk/live/"]]];
[super viewDidLoad];
}
- (void)webViewDidStartLoad:(UIWebView *)liveView
{
// starting the load, show the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)liveView
{
// finished loading, hide the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (void)liveView:(UIWebView *)liveView didFailLoadWithError:(NSError *)error
{
// load error, hide the activity indicator in the status bar
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
// report the error inside the webview
NSString* errorString = [NSString stringWithFormat:
#"<html><center><font size=+5 color='red'>An error occurred:<br>%#</font></center></html>",
error.localizedDescription];
[self.liveView loadHTMLString:errorString baseURL:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
if ( [self.liveView loading] ) {
[self.liveView stopLoading];
}
self.liveView.delegate = nil; // disconnect the delegate as the webview is hidden
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (void)dealloc {
[liveView release];
[UIWebView release];
[LiveViewController release];
[super dealloc];
}
#end
Many thanks,
Ryan
[UIWebView release]; [LiveViewController release];
This is what make your app crash.
It's not valid to send a release message to a class itself.
What you've done with [liveView release]; is enough (with the call to [super dealloc];.)
You should also set the delegate to nil in the dealloc method as in the viewWillDisappear method self.liveView.delegate = nil;. This way you're sure to avoid any further message sent to the LiveViewController from the UIWebView.
You should read a bit more of documentation on Objective-C to better understand how it works.
Not sure if this is related but I noticed that you aren't setting yourself as the delegate anywhere in code which means that it must be connected in Interface Builder. Now when the view disappears, you are breaking that connection, but if the view were to re-appear and wasn't previously unloaded that connection will remain broken.
One of the most common reasons why an app may crash is to refer to or send a message to an object that has been already released from the memory. And this type of bug can be easily located using NSZombieEnabled and looking into the console message. So if you haven't already tried that, that's the first thing you must do.
The problem could be in LiveViewController but could be in the other view controllers as well. I wouldn't believe the problem is 100% in LiveViewController because the view controller wouldn't try releasing its view when the view is not shown unless it gets a memory warning. And you run the app using the simulator, it's unlikely it will have a memory warning unless you simulate one.
You would probably know that a view controller never create a view unless the view is used by an object. One of the other view controllers may have a silly bug in its view loading process which causes a crash. Or, you might have released another view controller by mistake. Make 100% sure that the other view controllers have no problem showing their views on their own, when you keep changing between their views (without showing LiveViewController).
So what I would do is to try NSZombieEnabled and check if it accesses a released object, and if it does, what the object is. Also, I will make a double check that the problem is related to LiveViewController. If it doesn't help I would log a message when LiveViewController and its liveView is deallocated (for liveView you need to subclass it). Because delegate property almost always does not retain an object, if the LiveViewController object is released (which shouldn't happen) and liveView still has a reference to it in the delegate property it will make a crash.
Crashes like this are almost always related to releasing an object that has already been released and deallocated.
Let XCode help you find the error. In XCode 4:
- In the toolbar, select the scheme list, and select 'Edit Scheme'
- Select the 'Run Your.app' in the list on the left.
- Under 'Environment Variables', add the following name/value pairs in the appropriate columns:
CFZombieLevel 3
NSZombieEnabled YES
Now when debug your app, you will get a message telling when -release is called on an object that already has a -retainCount of zero. Now you have a good clue to start your investigation.
Note that these flags prevent objects from being deallocated, so it is best to turn them on as needed to prevent out of memory errors.
I'm dealing with viewDidUnload and dealloc methods and I've founded a problem when calling [super dealloc]; in parent view controller.
I have a lot of view controllers with custom code which I have putted outside on a parent view controller. So, when defining my view controllers I set a reference to the super class:
#interface LoginViewController : AbstractViewController
Then, at the dealloc method I call the AbstractViewController dealloc method:
//(Login View Controller code)
- (void)dealloc {
[user release];
[passwd release];
[super dealloc];
}
[super dealloc] execute the following code:
//(Abstract View Controller code)
- (void)dealloc {
[dbUtils release];
[loadingView release];
[super dealloc];
}
If I simulate a memory warning on iPhone Simulator, the following exception is thrown:
2010-03-03 11:27:45.805 MyApp[71563:40b] Received simulated memory warning.
2010-03-03 11:27:45.808 MyApp[71563:40b] *** -[LoginViewController isViewLoaded]: message sent to deallocated instance 0x13b51b0
kill
quit
However, if I comment the [super dealloc] line in AbstractViewController the exception is not thrown and my app still running.
Thank you for your help once again!
It looks like you're freeing your view controller but then trying to use it again. When you free your view controller because of the memory warning, remember to set the pointer to nil so you don't accidentally use it again. i.e. something like
[myLoginViewController release]; //!< Triggers the dealloc call
myLoginController = nil; //!< Makes sure we never use it again
or, if myLoginViewController is a property you can do this in a neater way :
self.myLoginViewController = nil;
Hope that helps,
Sam
Notice -didReceiveMemoryWarning does not trigger -dealloc, it triggers -viewDidUnload. So I guess it is your implementation of -viewDidUnload that does something wrong that causes the controller's final retention to be released so -dealloc is called. I've just encountered this problem in my code which is caused by a retain recycle that is released in -viewDidUnload.
This happened to me and took a while to figure out. One of the delegates was trying to send a message because the delegate was self and it was a strong reference. when I made it weak it seems to have fixed it.
I am having a problem with accessing public variable 'activity', which is a UIActivityIndicatorView type, see class declaration below in QuickStartViewController.h:
#interface QuickStartViewController : UIViewController <ABPeoplePickerNavigationControllerDelegate> {
#public
IBOutlet UIActivityIndicatorView *activity;
}
#property (nonatomic, retain) UIActivityIndicatorView *activity;
#end
The function is called from another class:
#import "QuickStartViewController.h"
#interface NumberValidator : QuickStartViewController....
See below:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[activity startAnimating];
NSLog(#"This function is called, but [activity startAnimating] still doesn't work...");
}
Note: [activity startAnimating] works fine when called within the QuickStartViewController class.
Do you have any suggestions as to why [activity startAnimating] is not working?
The IBOutlet macro indicates that the UIActivityIndicatorView will be constructed and assigned when an instance of QuickStartViewController or NumberValidator are instantiated via NSBundle's +loadNibNamed:owner:options: or calling UIViewController's initWithNibName:bundle:
If you are not instantiating your NumberValidator via it's nib, then the activity property will not be assigned. If you are constructing it via a nib, then you have not assigned the outlet with an appropriate UIActivityIndicatorView in Interface Builder, by CTRL+Dragging your UIActivityIndicatorView to your controller.
I would start by setting a breakpoint in -connectionDidFinishLoading: and verifying that activity is not nil.
This probably works - i.e. the activity indicator starts animating. There may be another problem, though - the GUI is not refreshed until you stop processing the connectionDidFinishLoading method, and therefore it seems that [activity startAnimating] doesn't work. (You can test this by not calling [activity stopAnimating] - it should show up eventually.)
See e.g. this thread (connectionDidFinishLoading - how to force update UIView?) and my response.
Is it (a) not compiling, (b) crashing when it hits there, or (c) just not doing anything? My suspicion is that it's (c), and it's because you don't have an activity indicator there. Try logging the value of activity to the console, and verify its a valid object.
Thanks for the quick responses.
The connectionDidFinishLoading does successfully execute, and I have placed NSLogs to confirm. However, the startAnimating doesn't.
Note:
If I do [activity startAnimating]; within the following then it works...:
QuickStartViewController.m (not NumberValidator.m):
- (IBAction)showPicker:(id)sender {
[activity startAnimating];
...
}