Using Instruments to improve memory-management with modal view controllers - objective-c

I feel like I don't understand something fundamental here. I've been working on memory management in my app while using Instruments to check out live allocations. I have a modal view controller (settingsViewController) that has an image for a background. One thing I noticed was that even after settingsViewController dealloc is called, there still is a live Malloc 520 KB, ImageIO is the responsible library. I'd expect live memory to drop back down after dismissing settingsViewController. Is the UIImageView still hanging around somewhere?
Here is how I load the image in viewDidLoad, as well as dismiss the view controller when I'm finished.
- (void)loadView {
[super loadView];
////// background ////////
UIImageView *background = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"settings_background.png"]];
[self.view addSubview:background];
[background release];
//////////////////////////
}
- (void)viewDidLoad {
[super viewDidLoad];
///////// done button //////////
UIBarButtonItem *done = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:#selector(doneButtonPressed)];
self.navigationItem.leftBarButtonItem = done;
[done release];
////////////////////////////////
}
- (void) doneButtonPressed {
[self dismissModalViewControllerAnimated:YES];
}
- (void)dealloc {
NSLog(#"settingsViewController dealloc"];
[super dealloc];
}
At this point, this is all that is in the settingsViewController, so no need to do anything special in dealloc, right? Here is how I'm showing testViewController in the rootViewController.
- (void) loadSettingsView {
SettingsViewController *settingsViewController = [[SettingsViewController alloc] init];
UINavigationController *settingsNavigationController = [[UINavigationController alloc] initWithRootViewController:settingsViewController];
[self presentModalViewController:settingsNavigationController animated:YES];
[settingsViewController release];
[settingsNavigationController release];
}
I'd like to make sure I understand what is going on before moving forward. I have several different modal view controllers, each with a different image as a background. Since each one creates a Malloc 520 KB, I end up using 2-3 MB of precious memory for no good reason. What is holding on to that 520 KB?

When you use the +[UIImage imageNamed:] method, the framework caches the image data for you. That's why you see it hold onto some memory even after your view is released. If you're working in the simulator and you want to see it release that memory, send the simulator a memory warning after you've dismissed your view. The image framework should then release the cached image data.

Related

inserting an image in view or delegate

Right now my code was an app delegate, a view controller, and a main view
right now i'm just trying to insert a large image as the background, but i can't figure out where to put it and i'm getting an error when i run a simulation
<Error>: CGContextRestoreGState: invalid context 0x0. This is a serious error. This application, or a library it uses, is using an invalid context and is thereby contributing to an overall degradation of system stability and reliability. This notice is a courtesy: please fix this problem. It will become a fatal error in an upcoming update.
- (void) applicationDidBecomeActive: (UIApplication *) application {
NSLog(#"AAAAAAAAAA");
[self startGameLoop];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSLog(#"AAAAA");
mainView = [[MainView alloc] initWithFrame: [UIScreen mainScreen].applicationFrame];
mainView.backgroundColor = [UIColor grayColor];
[self.window addSubview: mainView];
[self.window makeKeyAndVisible];
[self startGameLoop];
return YES;
}
- (void) startGameLoop {
quack = [NSTimer scheduledTimerWithTimeInterval:.0303 target:self selector:#selector(loop) userInfo:nil repeats:YES];
NSLog(#"Game Loop timer instance: %#", quack);
mainView.backgroundColor = [UIColor grayColor];
}
- (void) loop {
NSLog(#"BBBBBB");
pic = [UIImage imageNamed:#"f1.png"];
[pic drawAtPoint:CGPointMake(160, 0)];
[mainView setNeedsDisplay];
}
- (void) drawRect: (CGRect) rect {
maps = [UIImage imageNamed:#"map.png"];
[maps drawAtPoint:CGPointMake(W/2, 0)];
}
You're calling a method ( [pic drawAtPoint:] ) that requires a "current" graphics context, outside of a graphics context. UIKit automatically creates one behind the scenes before any call to drawRect:.
Also, your drawRect must be in the MainView class, not within the UIApplicationDelegate.
Try removing [pic drawAtPoint:CGPointMake(160,0)] from within loop.
If you want to draw it every loop, it must be done within the drawRect method, within the MainView class.
Also, you mentioned you're using a view controller but I see none referenced in your code.
But if you only want to move an image around, you may want to consider NOT calling setNeedsDisplay. Rather, create a UIImageView, add it to your view, and setting its center property within the loop method.

Unbalanced calls to begin/end appearance transitions for <xxViewController: 0xaa82610>

I tried to add TapforTap Ads to my iPhone app, the ad appear but I show this message in console "Unbalanced calls to begin/end appearance transitions for ." after any navigation of pages and then the app crash, the below code for calling TapForTap Ads. How I can solve this problem?
- (void)viewDidLoad
{
[super viewDidLoad];
CGFloat y = self.view.frame.size.height - 50.0;
TapForTapAdView *adView = [[TapForTapAdView alloc] initWithFrame: CGRectMake(0, y, 320, 50) delegate: self];
[self.view addSubview: adView];
[TapForTapAppWall prepare];
[TapForTapAppWall showWithRootViewController: self]; // or possibly self.navigationController
}
Thanks A lot
I don't know it for sure. But try this:
- (void) ShowTapAd {
CGFloat y = self.view.frame.size.height - 50.0;
TapForTapAdView *adView = [[TapForTapAdView alloc] initWithFrame: CGRectMake(0, y, 320, 50) delegate: self];
[self.view addSubview: adView];
[TapForTapAppWall prepare];
[TapForTapAppWall showWithRootViewController: self]; // or possibly self.navigationController
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelectorOnMainThread:#selector(showTapAd) withObject:nil waitUntilDone:NO];
}
The run time warning that you got appears when you hussle with two (or more) segue, Navigation Controller push or present modally types of processes. Meaning when you initiate one before the former was totally finished.
I am surprised that those things can happen when you call something within viewDidLoad. So you may find out that the root cause is not located within the code sniplet that you have shown. But if it is then this could do the trick.
What it does:
It just makes sure that your setup of the ad view is performed after viewDidLoad is properly finished. As viewDidLoad runs on the main tread as all UI related stuff does (or should do), the current appearance transistion should be finised by then.
Hope, this helps. Again, it is just a guess.

Memory problems using arc

i'm new on Objective-C programming and i'm having the typical memory problems. I must do an app based on navigation controller and when passing few views (using push view controller), load an animation of 100 images. In the simulator works well, but on the phone not... I open up different animations and then it closes. I'm using arc to avoid that, but it seems not to be working. I've also tried to disable arc and release the UIImageView manually but it crashes even quickly. Here's an example of one of that views:
//.h
#interface Gegant_nou : UIViewController {
IBOutlet UIImageView *ImageViewGegant;
}
#property (nonatomic, strong) UIImageView* ImageViewGegant;
//.m
- (void)viewDidLoad {
[super viewDidLoad];
UIBarButtonItem *rigthButton = [[UIBarButtonItem alloc] initWithTitle:#"Detalls" style:UIBarButtonItemStyleBordered target:self action:#selector(canviarDetalls)];
self.navigationItem.rightBarButtonItem = rigthButton;
[rigthButton release];
ImageViewGegant.animationImages =#
[[UIImage imageNamed:#"0001.png"],
[UIImage imageNamed:#"0002.png"],
. load all the images
.
[UIImage imageNamed:#"0099.png"],
[UIImage imageNamed:#"0100.png"]];
ImageViewGegant.animationDuration = 4;
ImageViewGegant.animationRepeatCount = 0;
[ImageViewGegant startAnimating];
[self.view addSubview:ImageViewGegant];
self.title = #"Gegant nou";
[ImageViewGegant release];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (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{
[super viewDidUnload];
[ImageViewGegant release];
}
Any idea of why happens? Thank you for helping me!
It would be easier to assist you if you provided some crashlog or stacktrace to provide more information about where the problem lies.
Are you totally sure the problem is memory related at all? Since you are using ARC it is pretty unlikely unless you have parts of your codebase where you don't use ARC or if you use c libraries like CoreGraphics etc, where you still need to retain/release even if you use ARC.
If you do have memory problems with say overrelease and you get a EXC_BAD_ACCESS crash, you can try to enable zombies in your app by Product -> Edit scheme.. -> Diagnostics and then "Enable Zombie objects". This will hopefully provide more information of the objects that are causing the problem.
On a completely different topic, I would strongly advice you to write all instance variables with camelcase. It is confusing to read, most developers will assume "ImageViewGegant" is the name of a class.
Problem solved! What was happening was that I was using UIImageNamed to load the animation, and although I was using ARC, UIImageNamed loads the image but don't releases it, so it loads all the frames and fills the memory very quickly. Now I don't have that memory problems. Thanks for all!

UIPageViewControllerSpineLocation Delegate Method Not Firing

Major head-scratcher all day on this one :-(
I have an instance of a UIPageViewController that does not appear to be firing the delegate method:
-(UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
I have tried various methods of displaying the UIPageViewController and have settled on a programatic approach (as opposed to a Storyboard one) that appears to be working correctly, with one exception... when rotating the iPad to landscape the spine does not appear mid-point as expected. I simply cannot find out why the delegate method does not get called.
Code Explanation (simplified for example)
Consider three classes as follows:
RootViewController - loaded when the app starts
PageViewController - loaded by RootViewController upon user initiation
PageContentViewController - loaded by PageViewController when pages are needed
Fairly self-explanatory. The RootViewController is loaded by the app upon launch. When the user taps an image within this view controller's view (think magazine cover opening a magazine) it launches the PageViewController as follows:
PageViewController *pvc = [[PageViewController alloc] initWithNibName:#"PageView"
bundle:[NSBundle mainBundle]];
pvc.view.frame = self.view.bounds;
[self.view addSubview:pvc.view];
In the actual app there is animation etc to make the transition all nice, but essentially the PageViewController's view is loaded and takes fullscreen.
PageViewController
This is the workhorse (only relevant methods shown). I have tried various examples from the infinite world of Google and written directly from the Apple docs...
#interface PageViewController : UIViewController <UIPageViewControllerDelegate, UIPageViewControllerDataSource>
#property (nonatomic, strong) UIPageViewController *pageViewController;
#property (nonatomic, strong) NSMutableArray *modelArray;
#end
#implementation TXCategoryController
-(void)viewDidLoad
{
[super viewDidLoad];
// Simple model for demo
self.modelArray = [NSMutableArray alloc] init];
for (int i=1; i<=20; i++)
[self.modelArray addObject:[NSString stringWithFormat:#"Page: %d", i]];
self.pageViewController = [[UIPageViewController alloc]
initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pageViewController.delegate = self;
self.pageViewController.dataSource = self;
PageContentViewController *startupVC = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
startupVC.pageLabel = [self.modelArray objectAtIndex:0];
[self.pageViewController setViewControllers:[NSArray arrayWithObject:startupVC]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
self.pageViewController.view.frame = self.view.bounds;
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerBeforeViewController:(UIViewController *)viewController
{
// Relevant code to add another view...
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController
viewControllerAfterViewController:(UIViewController *)viewController
{
// Relevant code to add another view...
}
-(UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController
spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
// Setting a break point in here - never gets called
if (UIInterfaceOrientationIsPortrait(orientation))
{
// Relevant code to create view...
return UIPageViewControllerSpineLocationMin;
}
// Relevant code to create 2 views for side-by-side display and
// set those views using self.pageViewController setViewControllers:
return UIPageViewControllerSpineLocationMid
}
#end
This all works perfectly well as I mentioned earlier. The PageViewController's view gets shown. I can swipe pages left and right in both portrait and landscape and the respective page number appears. However, I don't ever see two pages side-by-side in landscape view. Setting a breakpoint in the spineLocationForInterfaceOrientation delegate method never gets called.
This is such a head-scratcher I have burned out of ideas on how to debug/solve the problem. It almost behaves like the UIPageViewController isn't responding to the orientation changes of the device and therefore isn't firing off the delegate method. However, the view gets resized correctly (but that could be just the UIView autoresizing masks handling that change).
If I create a brand new project with just this code (and appropriate XIb's etc) it works perfectly fine. So something somewhere in my actual project is causing this. I have no idea where to continue looking.
As usual, any and all help would be very much appreciated.
Side Note
I wanted to add the tag 'uipageviewcontrollerspinelocation' but couldn't because it was too long and I didn't have enough reputation (1500 required). I think this is a devious ploy on Apple's part to avoid certain tags in Stackoverflow... ;-)
Finally found the problem. It was something of a red herring in its symptoms, but related just the same.
Putting a break point in the shouldAutorotateToInterfaceOrientation: method was a natural test to see if the UIViewController was even getting a rotation notification. It wasn't which led me to Apple's technical Q&A on the issue: http://developer.apple.com/library/ios/#qa/qa1688/_index.html
The most relevant point in there was:
The view controller's UIView property is embedded inside UIWindow but alongside an additional view controller.
Unfortunately, Apple, in its traditional documentation style, doesn't provide an answer, merely confirmation of the problem. But an answer on Stack Overflow yielded the next clue:
Animate change of view controllers without using navigation controller stack, subviews or modal controllers?
Although my RootViewController was loading the PageViewController, I was doing it as a subview to the main view. This meant I had two UIViewController's in which only the parent would respond to changes.
The solution to get the PageViewController to listen to the orientation changes (thus triggering the associated spine delegate method) was to remove addSubview: and instead present the view controller from RootViewController:
[self presentViewController:pac animated:YES completion:NULL];
Once that was done, the orientation changes were being picked up and the PageViewController was firing the delegate method for spine position. Only one minor detail to consider. If the view was launched in landscape, the view was still displaying portrait until rotated to portrait and back to landscape.
That was easily tweaked by editing viewDidLoad as follows:
PageContentViewController *page1 = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
NSDictionary *pageViewOptions = nil;
NSMutableArray *pagesArray = [NSMutableArray array];
if (IS_IPAD && UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
{
pageViewOptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:UIPageViewControllerSpineLocationMid]
forKey:UIPageViewControllerOptionSpineLocationKey];
PageContentViewController *page2 = [[PageContentViewController alloc] initWithNibName:#"PageContent" bundle:nil];
[pagesArray addObject:page1];
[pagesArray addObject:page2];
}
else
{
[pagesArray addObject:page1];
}
self.pageViewController = [[UIPageViewController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl
navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal
options:pageViewOptions];
self.pageViewController.delegate = self;
[self.pageViewController setViewControllers:pagesArray
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:NULL];
Job done and problem solved.

UItableView is not refreshing data after loading for second time the View

I'm having some trouble with a UITableView. It loads perfectly the first time I open it, but after the second try its not getting into the viewDidLoad method which causes the data not be refreshed on my tableview. I also made the proper release on my dealloc method. Any ideas about this? I've looked thru google but I didnt get anything useful. Thanks a lot for all the help you can provide me!
Here are my viewDidLoad and viewDidUnload methods
- (void)viewDidLoad
{
[super viewDidLoad];
saved_news.rowHeight =85;
addButton = [[UIBarButtonItem alloc] initWithTitle:#"Edit" style:UIBarButtonItemStyleBordered target:self action:#selector(edititems:)];
[self.navigationItem setLeftBarButtonItem:addButton];
[self setSaved:[CoreData sharedInstance].selectItems];
[saved_news reloadData];
}
- (void)viewDidUnload
{
[super viewDidUnload];
saved = nil;
saved_news = nil;
addButton =nil;
}
viewDidLoad and viewDidUnload are typically called only once during the lifetime of a controller (unless there's a low memory situation). I think what you are looking to use here are the viewDidAppear: and viewDidDisappear: methods.
Call
- (void)viewWillAppear:(BOOL)animated;
- (void)viewDidAppear:(BOOL)animated;
as you require load table here
[Yourtablename reloaddata];
Hope this helps..