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.
Related
I need to change the URL of a WebView (located in WebViewViewController) using buttons in View Controller 1 (called PracticeViewController). I totally understand how to change the web View when it is in the same View Controller as the buttons, but I don't understand how to get the buttons to affect the WebView when the WebView is in a different ViewController than the buttons. Below is the code I currently have:
PracticeViewController.h
#import <UIKit/UIKit.h>
#interface PracticeViewController : UIViewController
- (IBAction)passGoogleButton:(id)sender;
- (IBAction)passYahooButton:(id)sender;
- (IBAction)passBingButton:(id)sender;
/* ok I tried to make this as clear as possible, and you can look at the other files if you don't understand what I mean, but at its core I want to change the web site loaded by the Web View by clicking on the buttons in the other View Controller */
#end
space (BTW is there an easier way to post on StackOverFlow Besides pressing space 4 times for every line of code? am I missing something?)
PracticeViewController.m
#import "PracticeViewController.h"
#import "WebViewViewController.h"
#interface PracticeViewController ()
#end
#implementation PracticeViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)passGoogleButton:(id)sender {
WebViewViewController.webSiteURL = #"http://www.google.com";
/* This supposedly works on a tutorial I saw, but Xcode flags it with the error 'Property webSiteURL not found on object of type 'WebViewViewController'' */
}
- (IBAction)passYahooButton:(id)sender {
WebViewViewController.webSiteURL = #"http://www.yahoo.com";
//same error as above
}
- (IBAction)passBingButton:(id)sender {
WebViewViewController.webSiteURL = #"http://www.bing.com";
//same error as above
}
#end
space
WebViewViewController.h
#import <UIKit/UIKit.h>
#interface WebViewViewController : UIViewController
{
UIWebView *myWebView;
NSString *webSiteURL;
}
#property (weak, nonatomic) IBOutlet UIWebView *myWebView;
#property (strong, nonatomic) NSString *webSiteURL;
/* I don't entirely understand what 'strong' means but I know it has to do with memory retention and that it (supposedly) should be used when I wan to transfer the value of the string 'webSiteURL' to the other ViewController */
#end
space
WebViewViewController.m
#import "WebViewViewController.h"
#interface WebViewViewController ()
#end
#implementation WebViewViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[myWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:webSiteURL]]];
/* As you can see above, the WebView loads from the string 'webSiteURL' but I can't figure out how to assign different values to it based on what button is clicked in the other View Controller so that it will run in ' - (void)viewDidLoad.' I already know how to transfer the value of the string to other things within another view controller (like into a label or a text field) but what I really need to know is how to get the string 'webSiteURL' to change before the view is loaded */
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
You have the right idea but you are missing a step here. You are never creating an instance of webViewController.
What you need somewhere is...
webViewcontroller *MyWebController = [[webViewController alloc] init];
It depends on how you are presenting the WebViewController. If you simply want the button to pop up the WebViewController modally you could use the following:
- (IBAction)passGoogleButton:(id)sender {
webViewController *myWebVC = [[webViewController alloc] init];
[myWebVC setWebSiteURL:#"http://www.google.com"];
[self presentViewController:myWebVC animated:YES completion:nil];
}
So, you can see here. You create the instance of the webViewController called myWebVC. Then you pass the string to it. So when myWebVC loads and it hits viewDidLoad, it will use the string that you passed in already and load the web view with that content. Hope that helps.
Also, Make sure you #synthesize your properties in your .m files.
You can use nsnotificationcenter to accomplish this task.
First register to notificationcenter in WebViewController, then send notification from another controller.
Here's a sample for registering to notificationcenter in WebViewController
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(nMessageOpenURL:) name:#"nMessageOpenURL" object:nil];
This is the code to run the logic when you receive the notification. (in WebViewController)
- (void)nMessageOpenURL:(NSNotification *)note {
//run the logic in here.
}
And finally this is the code where you trigger the notification.
[[NSNotificationCenter defaultCenter] postNotificationName:#"nMessageOpenURL" object:nil];
Here's the Apple documentation.
I got to know that you are trying to pass the value of the string of url between two view controllers but you are not getting it right!!
So if you are using storyboard you can try out passing this value using Segue. For Eg: you can use prepareForSegue to set the different values using if-else construct.
And if you are not using segue the best practice to pass those values will be using delegate.
Just create a delegate method and pass the url string as an argument.
Hope this works :)
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.
I have a view with a navigation bar in my iphone app. The navigation bar has a back button on it to go the the previous view. Within the view is a webview. If the webview is loading while the user presses the back button on the navigation bar, the app crashes with the error message:
[WebViewController respondsToSelector:]: message sent to deallocated instance
I am assuming that I release some things when the view unloads. I believe my viewWillDisapear method takes care of stopping the webview from loading... But still, when I press back while the view is not fully loaded, it crashes.
It seems to happen when I let the view finish loading about 20% of the time - and only if I press the back button really quickly after the page finishes loading.
Here is the code that I believe runs during this shinanigans....:
- (void)viewDidLoad {
[super viewDidLoad];
webView.scalesPageToFit = YES;
[webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString: url]]];
[addressBar setText:url];
}
and then when the webview is loading:
- (void)webViewDidStartLoad:(UIWebView *)webView {
if (first) {
//mtns is a uiimageview
mtns.alpha = .8;
first = NO;
}
[activityIndicator startAnimating];
}
and after a suggestion, I added this method:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.webView stopLoading];
self.webView.delegate = nil;
}
I don't know why it is crashing so I am sort of at a loss
Thanks,
R
You could override -viewWillDisappear: to stop the UIWebView from loading and set the delegate to nil.
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.webView stopLoading];
self.webView.delegate = nil;
}
This may be because of before the execution code that you have written in viewwillDisapper application crashes.try to put breakpoint and check whether viewwillDisapper method is called or not.If its not called then write the code that mark has suggested before the application navigates to second view controller.
You have to be sure to set the delegate property of the web view to nil before it goes away. Do this both in your dealloc and viewDidUnload methods, since viewDidUnload is not always called before dealloc. What’s happening is that after your view controller is deallocated, the web view still has a reference to its address, and is trying to send it a message (-respondsToSelector:).
- (void)dealloc {
webView.delegate = nil;
[super dealloc];
}
- (void)viewDidUnload {
webView.delegate = nil;
[super viewDidUnload];
}
I am facing a bit strange problem with implementation of UIAccelerometer.
I have a UITableViewController where I don't wanna use UIAccelerometer, but after pressing on of the rows I wanna activated one inside a UIViewController, everything is fine when I use simulator, but when I use device iPhone 3G to test it, I got EXC_BAD_ACCESS by pressing return button.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
ShakeControl *percView = [[ShakeControl alloc] init];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:percView animated:YES];
[percView release];
}
It works fine when I disable [percView release];, but it does not sounds like solution for.
Any Idea would be appreciated.
Shake control implementation:
- (void)viewDidLoad {
[super viewDidLoad];
[[UIAccelerometer sharedAccelerometer] setUpdateInterval:1.0 / kUpdateFrequency];
[[UIAccelerometer sharedAccelerometer] setDelegate:self];
}
- (void)viewDidUnload {
[super viewDidUnload];
[[UIAccelerometer sharedAccelerometer] setDelegate:nil];
}
Thx
The viewDidUnload method will get called only in specific case, such as memory warning. You have to remove the delegate in dealloc too.
Try releasing it after you dismiss it, I'm not sure if pushViewController retains.
You could setting the environment variables NSZombiesEnabled & NSAutoreleaseFreedObjectCheckEnabled with a value of 1.
This will prevent objects from really being deallocated, then you should get console logs indicating where the over-release is coming from.
You could use Run With Performance Tools -> Allocations once you know which object is being over-released and see every location where a retain/release was called to spot the imbalance.
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.