I am struggling with a problem that looks like simple but it is making the app run size to 30-35 MB. The app is ARC enabled. Here is the scenario.
1) I invoke a UIViewcontroller from within my method (the viewController instance is local to the method)& after pushing it to NavigationController I am setting the local instance as nil.
btMapViewController *routeMap = [[btMapViewController alloc]init];
[routeMap setSourcLocation:[txtsource text]];
[routeMap setDestinationLocation:[txtDestination text]];
[routeMap setNightFareOn:addNightCharge];
[self.navigationController pushViewController:routeMap animated:YES];
routeMap = nil;
2) The newly pushed controller initializes a MKMapView & plot the routes on it. The app now runs on a memory of 35-40 MB which is 5 times more than what it was running before step 1.
#interface btMapViewController ()
#property(nonatomic, strong) MKMapView *mapView;
#end
3) Now if I pop out the UIViewcontroller (the one loaded in step 1), the app runs on a memory of 30-34 MB.
I check the Memory trace, its clear. Then who is holding the memory ?
Is that the MKMap is the part of Interface Implementation is leading to this problem or shall I make it private to the btMapViewController class. ?
Related
I have developed application using ARC. In one of my UIViewController there are number of sub controllers (Like Buttons, Labels, Textfields, Textview, Scrollview) which all are having its IBOutlet. Here issue is that,I am using iOS 6.0.
With iOS 6.0 viewDidUnload method is deprecated. So at the time of Pop, this method is not called. I have checked with "product--> profile--> allocation" here whenever this controller is pushed in the navigation stack it increases the live memory Bytes but on pop Memory bytes don't decrease. Because of this after using an application for some time I am getting Received Memory Warning & application is going to be crashed in random instances.
Is there any alternative way to handle this kind of backend memory management issue.
First check in .h file that you property-sythesized with retain or not if with retain then set strong instead of retain like bellow..
#property ( nonatomic, strong) IBOutlet UITextField *yourTextField;;
UPDATE - Cause found!... please read below & suggest the solution:
While creating a video to show this issue, I have found why does that happen...
Any Control/Element that is defined between #imports & #implementation DetailViewController in .m file is lost by the original detVC when a new instance is created of VC.
Example:
I am including a video that re-creates this issue. As shown, all controls work except a UISegmentedControl & a UILabel.. both of which are defined in DetailViewController.m as:
#import "DetailViewController.h"
UISegmentedControl *sortcontrol;
UILabel *incrementLabel;
#implementation DetailViewController
Video Link - http://www.youtube.com/watch?v=2ABdK0LkGiA
I think we are pretty close.. Waiting for the answer!!
P.S.: I think this info is enough can lead us to the solution, but if needed I can share the code too.
EARLIER:
There's a detailViewController (detVC) in our app which is normally 'pushed' from a UITableViewController.
Now we are also implementing Push Notifications which would 'modally' present the same detVC whenever the notification arrives, which in turn can happen over any view controller.
It works all fine until the detVC through notification is presented while another instance of detVC was already the active view controller (pushed earlier through TableView).
The problem happens when the presented detVC is dismissed - the pushed detVC 'looses' control of about everything - It is not pointing to the same data as earlier & even the UISegmentedControl on Navigation Toolbar points to -1 index on pressing any index!
Why does such thing happen when we are creating a separate instance of detVC? Why does it affect the earlier detVC? How can it be resolved / What about Objective C needs to be learned here? (FYI, we are using ARC in the project)
Thanks
EDIT - Some code:
When Pushed:
ProductDetailViewController *detailViewController = [[ProductDetailViewController alloc] init];
detailViewController.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:detailViewController animated:YES];
detailViewController.serverOffset=serverOffset;
detailViewController.prdctIndex = indexPath.row;
When presented through Notification:
- (void)displaySingleProduct:(NSDictionary*)userInfo{
ProductDetailViewController *onePrdVC = [[ProductDetailViewController alloc] init];
UINavigationController *addNav = [[UINavigationController alloc] initWithRootViewController:onePrdVC];
onePrdVC.justOne=YES;
onePrdVC.onePrdIDtoLoad=[[[userInfo valueForKey:#"prd"] valueForKey:#"id"] intValue];
[self.navigationController presentModalViewController:addNav animated:YES];
}
There is a Product class in detVC which gets the data from the values stored at the prdctIndex row in the SQLite table.
After dismissing the presented detVC through notification, the Product class of the pushed detVC starts pointing to the row which was used for Product class in presented detVC. And there is no such code in viewDidAppear which would alter Product class.
So, this, UISegmentedControl issue mentioned above & some other problems are causing the pain! Basically, the whole detVC acts weird - which re-works if we just pop & re-push it!
EDIT 2
I have more info on it which could lead to the cause.
If I run viewDidLoad on viewDidAppear like this:
if (!justOne) {
aProduct = [allProducts getProduct:(prdctIndex+1) one:NO];
[self viewDidLoad];
[self populateDetails];
}
the pushed detVC regains the control & every element/control starts re-working as expected (but not in the ideal way). So, it is quite clear that the separate instance of presented detVC does messes up the earlier pushed detVC & a re-setting up of all the controls / pointers is required through viewDidLoad.
Can any helpful conclusion can be derived from this info?
When in your code you write:
#import "DetailViewController.h"
UISegmentedControl *sortcontrol;
UILabel *incrementLabel;
#implementation DetailViewController
You are not defining these variables as instance variables (ivars), so they are not individual for each instance. There are three ways to define ivars.
1) The traditional way, in your .h file.
#interface DetailViewController{
UISegmentedControl *sortcontrol;
UILabel *incrementLabel;
}
2) Some additions to objective-C have added support for the next two ways. Like declaring them in your .m file.
#implementation DetailViewController{
UISegmentedControl *sortcontrol;
UILabel *incrementLabel;
}
3) If these ivars use properties to expose them, then you can simply leave out the explicit definition of them. So in .h:
#interface DetailViewController
#property (strong, nonatomic) IBOutlet UISegmentedControl *sortcontrol;
#property (strong, nonatomic) IBOutlet UILabel *incrementLabel;
and then in the .m file:
#implementation DetailViewController
#synthesize sortcontrol;
#synthesize incrementLabel;
Are you loading the view controller from a NIB? If so, it may be that the same instance of the view controller is used each time. You can log the address of your view controller in viewDidLoad and see if this is the case.
It actually depends on how your apps architecture has been created. If you are using TabBarController based application, here is the proper way to do so.
If two instances of the same class are interfering with each other, that strongly suggests that you are not storing all your state in instance variables. I would suspect that something in ProductDetailViewController stores its state in global or class data, and that your problem lives in there.
It's possible that you have done something silly like making ProductDetailViewController a forced singleton (overriding +alloc, which you should almost never do), but generally people remember when they've done that.
Never call viewDidLoad directly. That's only for the system to call. (I know you were just testing, but don't leave that in.)
But are you calling [super viewDidAppear:animated] in your viewDidAppear:? You have to call your superclass in that method.
I have a UIViewController that has an IBOutlet for a UILabel with the label wired up in the XIB.
#import <Foundation/Foundation.h>
#interface MyViewController : UIViewController {
IBOutlet UILabel *aLabel;
}
#end
According to iOS Programming: The Big Nerd Ranch Guide (2nd Edition) chapter 7
When [MyViewController] reloads its view, a new UILabel instance is created from the XIB file.
Thus it recommends releasing the label in viewDidUnload.
- (void)viewDidUnload {
[super viewDidUnload];
[aLabel release];
aLabel = nil;
}
Being a C# programmer, I've had it drummed into me that assigning nil/null to things is pointless. While I can see it makes more sense in Objective-C, it still grates my sense of code aesthetic slightly*. I removed it and it all worked fine.
However, when I tried to do a similar thing with MKMapView the application errors EXC_BAD_ACCESS while trying to load the NIB.
#import <Foundation/Foundation.h>
#interface MyViewController : UIViewController {
IBOutlet MKMapView *mapView;
}
#end
- (void)viewDidUnload {
[super viewDidUnload];
[mapView release];
mapView = nil; // Without this line an exception is thrown.
}
Why is there an error when mapView is not set to nil, but not when aLabel is not set to nil?
* I realise I need to adjust my sense of code aesthetic for the new language, but it takes time.
It turns out I was just flat out wrong about aLabel not being referenced. Not sure what made me think it wasn't.
However, that still leaves the question of why they're being referenced while the NIB is being loaded.
When the field or property is set, a release message is sent to the old value (either the synthesized properties set method sends the release message, or setValue:forKey: sends the message if it's a field). Because the old value has already been released, this results in EXC_BAD_ACCESS.
It's because of memory management, specifically the lack of garbage collection.
In C# (as you know) objects that are no longer in scope are removed. In objective-c, that doesn't happen. You have to rely on retain/release to tell the object when you are done with it.
There's a drawback to the objective-c reference counting method that your mapView bug exhibits. Calling release on an object might result in it being deallocated. However, your pointer to the object will still point to the same place - your object just won't be there anymore.
For example
// We create an object.
MyObject *object = [[MyObject alloc] init];
// At this point, `object` points to the memory location of a MyObject instance
// (the one we created earlier). We can output that if we want :
NSLog(#"0x%08X", (int)myObject);
// You should see a number appear in the console - that's the memory address that
// myObject points to.
// It should look something like 0x8f3e4f04
// What happens if we release myObject?
[myObject release];
// Now, myObject no longer exists - it's been deallocated and it's memory has been
// marked as free
// The myObject pointer doesn't know that it's gone - see :
NSLog(#"0x%08X", (int)myObject);
// This outputs the same number as before. However, if we call a method on myObject
// it will crash :
NSLog(#"%#", myObject);
In objective-c, if you try to call a message on nil, nothing happens. So if each time you are finished with an object and call release on it, you should also set it to nil - this means that if you try to use that pointer again, it won't crash!
viewDidUnload is usually called when the device receives a memory warning.
In the View stack there may be situations where the device can free up memory by releasing objects that aren't in use. Imagine you have a navigation stack with a number of view controllers. The interface view controllers lower on the stack are not accessible but are still using up memory. So its usually a good idea to nil out any interface elements that can't be accessed. These will then be reloaded with viewDidLoad when needed.
Generally in ViewDidUnload you should release any view objects that are created from a Nib file or allocated in your ViewDidLoad method
Your mapView is throwing an exception because your view controller is trying to access to MapView but the mapView has been released. When you set the outlet to nil any messages sent to it are ignored.
I've been trying to solve a problem to do with the image in my UIImageView being released due to memory warnings.
Here's my situation:
1) I declare a UIImageView in my .H:
IBOutlet UIImageView *fightStyleImage;
...
#property (nonatomic, retain) UIImageView *fightStyleImage;
2) I synthesize and dealloc in my .M:
#synthesize fightStyleImage;
...
...
-(void)dealloc{
[fightStyleImage release];
...
}
3) I attach the fightStyleImage to a UIImageView in Interface Builder.
4) Part of my app's experience allows a user to swipe on the device, to cycle through 5 different images. I am using the following code to achieve that:
// Load next image
UIImage *theImg = [UIImage imageNamed:#"fighter1.png"];
fightStyleImage.image = theImg;
Everything works really well, and I'm happy with the performance, experience, etc.
However, when I pop to another view controller (or to an MFMailComposeViewController, or an Address Book picker) and my device receives a memory warning (either through the simulator, or on the device), the following happens when I return to my original view controller: the iOS seems to jettison my UIImageView image (I assume because it is the most expensive object in memory), and I am left with a black screen (i.e. no image). If I begin to swipe again, the images return and continue to cycle properly.
I'd love to hear any thoughts on this, and thanks in advance for any solutions/insights anyone might have. In particular, I'm interested (of course!) in restoring my image that has been jettisoned from memory.
-SD
You need to properly implement the viewDidLoad and the viewDidUnload to setup and release your views. Read the iOS section of the Memory Management Programming Guide for assistance. I also recommend you move your IBOutlet declaration from the ivar to the property as so
UIImageView *fightStyleImage;
...
#property (nonatomic, retain) IBOutlet UIImageView *fightStyleImage;
The problem maybe due to using imageNamed: method as it caches the images into your memory and will be forced to free only when Low Memory warning occurs. Better go this way:
UIImage *image=[[[UIImage alloc]initWithContentsOfFile:imagePath]autorelease];
EDIT:
Solved the problem myself. Turned out it was a leftover in the dealloc method that caused a UIButton to be released twice...
I'm trying to display a UIViewController on top of another UIViewController like a popup. Problem is that the view seems to be getting overreleased. With NSZombieEnabled, I get the following error:
[CALayer release]: message sent to deallocated instance 0x784bf40
I use this code to add the view:
//self.someViewController is declared as (nonatomic, retain)
self.someViewController = [[[SomeViewController alloc] initWithDelegate:self] autorelease];
[self.view addSubview:self.someViewController.view];
Then later on, I remove the view like this:
[self.someViewController.view removeFromSuperview];
self.someViewController = nil;
Should earlier comments not solve this perhaps this may help. I'm assumning you've created your someViewController property like this
#property (nonatomic, retain) NSViewController* someViewController;
in which case I believe your code is correct (at least I can see how it should work) and you might be seeing a secondary crash here.
I.e. when you're calling
self.someViewController = nil;
this should be freeing the memory up immediately (assuming a frame has gone by where the VC exists so the autoreleased count has already been decreased). Therefore if you have ANOTHER object being used in that someViewController VC who still exists and has a delegate set to your someViewController object and is doing a background task it will cause a crash when it trys to call back to your now deallocated object. (If you don't free your VC you wouldn't see this crash)
For example if you have a MKMapKit being displayed in someViewController and the delegate is set to someViewController... if you have implemented the method in someViewController
mapViewDidFinishLoadingMap:(MKMapView*)mapView
the MKMapKit may still be calling this from another thread if you haven't destroyed MKMapView object before yours.
I would always set other object delegates pointing to your VC (like MKMapView) to nil before destroying the said VC it uses to avoid this risk. For PDF rendering (CALayer?) you may find you need to explicitly release the object too given this uses a different memory alloc/free paradigm.
Solved the problem myself. Turned out it was a leftover in the dealloc method that caused a UIButton to be released twice...