I am using the Apple UIPageViewController template from Xcode to create interactive photobooks. Everything works fine except that whenever I turn a page (create a new viewcontroller) the memory allocation goes up and up and up until the app crashes. It looks to me that the viewcontrollers never get 'released' (am I still allowed to use that word in an ARC environment?). It does not seem to be anything to do with the content of the pages because when I comment out all the content creation stuff in the ...DataViewController the memory still keeps going up and up every time I turn a page, not as spectacular as when a large image has been included but it still keeps creeping up.
There's been exactly the same question here: PageViewController: How to release ViewControllers added to it? but this one deals with a pre arc & storyboard situation. Adding autorelease is not permitted and it certainly seems that the compiler is NOT taking care of it. :-(
Any suggestions?
The problem turned out to be the never enough damned "UIImage imagedNamed" construct. It probably is all my own fault for not checking after I read somewhere that this had been fixed in a recent xcode release. So I assumed images were no longer being cached whereas the opposite was true. Once I changed all to the "UIImage imageWithContentsOfFile" the app started working smooth as a babies bottom.
I had the same problem building a picture book with very large images. I went to other question you provided a link for and that solved it for me. Adding "autorelease" frees up the memory.
In the UIPageviewcontroller delegate method "viewControllerAtIndex". I changed from:
// Create a new view controller and pass suitable data.
ContentViewController *contentViewController = [[ContentViewController alloc] initWithNibName:#"ContentViewController" bundle:nil];
and added autorelease
// Create a new view controller and pass suitable data.
ContentViewController *contentViewController = [[[ContentViewController alloc] initWithNibName:#"ContentViewController" bundle:nil] autorelease] ;
This wasn't included in the apple example but I'm also using xib for each page. I've been debugging this using instruments and saw memory reclaimed right away and dealloc being called when it wasn't previously.
found answer here....
https://stackoverflow.com/a/7934392/1212585
Related
I need to use a scratch effect for my game. here is an example code years ago.
https://github.com/oyiptong/CGScratch
It works just fine, but when i use it together with navigation controller, it crashes.
My project uses ARC, i flag the file as -fno-objc-arc. Here is the source code:
https://github.com/lifesaglitch/ScratchWithError
it crashes when i push the view controller, then pop, then reenter.
Edit:
When you convert all to arc, and flag the view controller that uses the scratch view as -fno-objc-arc, it works. But when you flag the scratch view as -fno-objc-arc instead, it crashes again. My project uses arc and i dont think i can convert my own view controller to be -fno-objc-arc.
Edit 2:
I modify the initialization code to:
scratchable = CGImageRetain([UIImage imageNamed:#"scratchable.jpg"].CGImage);
it does not crash anymore, but there's a memory leak. and CGImageRelease did get called once in dealloc method.
Use CGImageCreateCopy.
The reason for this is that you send a release to your CGImageRef at your dealloc, but if you inspect the actual CGImage object you'll see that it points to the same memory address each time (I guess it's part of Apple's optimizations, so it's just like you would have a static UIImage object and reference its CGImage).
So in your initWithFrame: you should get your scratchable like this:
UIImage *sci = [UIImage imageNamed:#"scratchable.jpg"]; // This guy is always the same
scratchable = CGImageCreateCopy(sci.CGImage);
PS: You had an actual leak with pixels, so you also need a CFRelease(pixels);
I tested, analyzed and measured the code and it seems to be OK now.
Here is also a link to the fixed project (I've also put a navigation controller and a button to push/pop) - and uses ARC of course.
This one works great for me https://github.com/moqod/iOS-Scratch-n-See !
In AppDelegate.m
[window addSubview:viewController.view];
You should instead replace it with:
[window setRootViewController:viewController];
The line of code works fine on the iOS Simulator 6.0, but is crashing when I try to run it on my iPhone, also running iOS6.
[menuView addSubview:mvc.view];
Why is this happening, and how can I fix it?
This is the more complete version of the code:
SDMenuViewController *mvc = [[SDMenuViewController alloc] init];
[menuView addSubview:mvc.view];
And this is what it is crashing with:
2012-10-08 21:32:32.423 CrunchCalculator1-2[21019:907] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not load NIB in bundle: 'NSBundle </var/mobile/Applications/EDD23933-CE20-4AFD-A2B1-CDD56AD658E8/CrunchCalculator1-2.app> (loaded)' with name 'SDNestedTableView''
*** First throw call stack:
(0x39cd03e7 0x35ece963 0x39cd0307 0x39ee0fd1 0x39ee05ff 0x39dd9031 0x39e0786d 0x39d63419 0xb20d9 0x39d63541 0x39da3d29 0x39d9fac5 0x39de1199 0xb17c5 0x39da4a71 0x39da45f5 0x39d9c803 0x39d44ce7 0x39d44775 0x39d441b7 0x31e145f7 0x31e14227 0x39ca53e7 0x39ca538b 0x39ca420f 0x39c1723d 0x39c170c9 0x39d9b43d 0x39d98289 0xb1523 0x3792fb20)
libc++abi.dylib: terminate called throwing an exception
Thanks!
I'm not quite sure how it worked on your simulator (when I tried it on mine, I got the crash you list in your original question). Anyway, you can fix it by looking at the following items:
The main problem is that the NIB was not included in the bundle. Add it to the project target's "Copy Bundle Resources", e.g.:
While you're looking at your "Copy Bundle Resources", you'll also want to include SDSubCell.xib, SDGroupCell.xib, and add all of those PNG files, too.
As an aside, while it doesn't apparently cause the crash, the "File Owner" base class in SDNestedTableView NIB refers to a class that doesn't exist anywhere in this project. That can't be good. Anyway, you probably want to change that to SDMenuViewController or SDNestedTableViewController;
It's a little unrelated to your crash, but as I look at the project, I see a worrying construct:
SDMenuViewController *mvc = [[SDMenuViewController alloc] initWithNibName:#"SDNestedTableView" bundle:nil];
[menuView addSubview:mvc.view];
You're creating a controller, grabbing its view, and either letting the view controller fall out of scope and be released (if you were using ARC) or leaking it (if not ARC).
I wasn't entirely sure from the original question whether you were doing addSubview as a way of transitioning to a new view (which is really bad practice) or whether you were doing view controller containment. As I look at the code, it appears you're doing the latter, though you're missing a few calls in your code. You might want to read up on view controller containment. And also check out that WWDC 2011 session 102.
Anyway, those two lines of code above with the view controller alloc/init and the subsequent addSubview will leak in your non-ARC project (and would crash it if you ever went to ARC) and your view hierarchy is out of sync with your view controller hierarchy. I'd suggest you might want:
SDMenuViewController *mvc = [[[SDMenuViewController alloc] initWithNibName:#"SDNestedTableView" bundle:nil] autorelease];
[self addChildViewController:mvc];
[mvc didMoveToParentViewController:self];
[menuView addSubview:mvc.view];
Note the autorelease on that first line.
View controller containment can be powerful, but you want to make sure you do some of this basic housekeeping.
One final update:
I notice that there are some bugs that are in this code. First, your use of currentSection in item:setSubItem:forRowAtIndexPath won't work. You're setting that based upon the last expandingItem. So, if you click on one of the main items before expanding either one, the program will crash. Probably best is to eliminate the currentSection variable altogether and in item:setSubItem:forRowAtIndexPath, use item.cellIndexPath.row rather than your variable currentSection.
Unfortunately, this fix leads to a more serious problem, there appears to be an iOS 6 bug in the SDNestedTable class, itself. If you run this on iOS 6 and you expand your all of your items, scroll to the bottom and then scroll back to the top, the program will crash because the cellIndexPath property of the SDGroupItem *item returned by item:setSubItem:forRowAtIndexPath can be deallocated! If you turn on zombies in iOS 6, you'll see cellIndexPath has been released. I went and downloaded the original version and see the same problem there. The problem appears to be that cellIndexPath in SDGroupCell is defined as an assign property (which means that if iOS determines it no longer needed the indexPath it created for its own purposes, it will be released even though SDGroupCell maintains an assign reference to this released object). Just change the cellIndexPath property of SDGroupCell from assign to retain, and this iOS 6 bug goes away. I've informed the developer of SDNestedTable of this issue, but this change to retain will fix the problem of the code crashing in iOS 6.
[Edit: The author of SDNestedTable agreed with my assessment of the issue, and he reports that this issue has been fixed the latest version. - Rob]
Best wishes.
You should probably use initWithNibName: insead of just init in the first line. Not sure regarding your specific issue, but certainly something to try.
It looks like you're trying to instantiate a nib called SDNestedTableView.nib and it isn't present. Is the nib included as a project member?
I'm writing a navigation app for iPhone at the moment and I'm having a very weird crash issue and was wondering if anyone had come across (and solved) this issue.
I have two views, both of which contain UITableViews and one that uses cells loaded from a nib. When I push and pop from one view to the other, after a couple of presses (usually 7 to 10) with everything loading and displaying as it should the app suddenly crashes. The debugger shows that CALayer was the last thing running, but I don't use any custom implementation of this class.
My first thought is that I've over-released an object, but after two days of playing with the code I can't identify any zombies.
Does anyone know what's going on here? Can post parts of code if required.
UPDATE:
Looks like zombies are being created on UIView delegate methods, namely viewWillAppear, viewDidAppear, viewWillDisappear, viewDidDisappear. Will investigate further tomorrow. :D
What you can do is to set breakpoints at the dealloc methods of the related classes, and see if the crash happens in one of the method. And also usually by looking at the callstack when the crash is happening, you can tell whether it's a memory related crash or not.
This is driving me nuts. I am using three20's TTTableViewController and when I get a memory warning, the screen goes white. Now, after reading on the three20 google group is seems that the tableView got released. But, I cannot for the life of me figure out a check to see if that is the case, then create it again.
I was using the following because I thought it would fix the issue, but it seems that it doesn't satisfy the if statement:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// If we don't have a datasource we need to reset it
if (!self.dataSource) {
// Create datasource again
}
}//end
Does anyone know what to do when this happens? The google group has been no help.
Are you subclassing TTTableViewController? I haven't used it before, but assuming it's just like UITableViewController...
How does your "viewDidUnload" look like? Are you releasing the tableview here? If so, you need to create tableview in viewDidLoad to match it.
No need to check if dataSource is available in viewDidAppear, because if you read View programming guide, it explains that memory warning will call "viewDidUnload" to give you a chance to clean up data that are created in "viewDidLoad".
i had the same issue and it drove me crazy as well.
Nobody mentions it in the three20 docs, but you shouldn't use UIViewController's initWithNibName function to add subviews. If you do, a memory warning will release these subviews.
Try to move your code from initWithNibName function to viewDidLoad function. I have noticed that some code need to be kept in the initWithNibName, such as navigation styles. However, any subviews added to the controller's view should be in the viewDidLoad function.
In general you should be careful to set up views in viewDidLoad rather than the class constructor. For instance, you should set up your launcher view in viewDidLoad rather than the constructor of your launcher view controller, otherwise your launcher will become empty after a memory warning.
In the case of TTTableViewController however this does not (usually) apply because you don't set up the table view manually. I had the same problem you had, and eventually tracked it down: I had redefined viewWillDisappear: and forgot to call [super viewWillDisappear:animated]. This meant that some of the flags that the Three20 controller maintains about the state of the view were not updated correctly.
I also found that it was beneficial to redefine didReceiveMemoryWarning to call [self setEditing:NO] before calling super; I found that the state of the table view got confused otherwise (this is not relevant if you don't use edit mode for your table).
Finally, there is a bug in Three20 which means that tables in loading/empty/error mode will not be restored properly; see a discussion in the blog post by TwoCentStudios and a proposed fix on github.
what are best pratices to reuse UIViewControllers? In many apps (including Apple's own examples: e.g. SQLiteBooks), UIViewControllers are allocated and initialized everytime, a UIViewController is pushed to the stack. This increases the use of memory with every new controller, because the objects stay in memory and aren't used again.
How to make it better?
This increases the use of memory with
every new controller, because the
objects stays in the memory and aren't
used again.
It should be released when the stack is popped though, as long as you have not got something else holding on to it. Check your dealloc methods are getting called.
Also if it is pushed to the stack, then you need to keep it around at least until it is popped (which automatically happens if you follow the standard patterns). So it is used again.
So following the standard pattern should already keep your memory usage as small as you can get away with.
This is what I do when creating a new viewcontroller and the memory is released when the view is removed from the window
MyViewController *mvc = [[[MyViewController alloc] initWithNibName:#"MyView" bundle:nil] autorelease];
[[self navigationController] pushViewController:mvc animated:YES];
Do you actually have a memory issue that you are trying to address or is this a case of premature optimization? I would say that unless there is a specific resource issue then the best practice would be to follow the standard view controller patterns.
Put a breakpoint in your view controller's dealloc function, and make sure it is called when you remove the view controller from the window. The memory shouldn't keep building up. If you're properly creating and autoreleasing your controllers (as LostInTransit shows above), the memory for each controller should be released when it is removed.
If you see that dealloc is not getting called, it means that somewhere in the app a reference to the view controller still exists.
Don't forget that a View Controller is not your view.
Views held by a view controller can unload, so view controllers themselves are very lightweight. If you want to keep the footprint really light you could nullify any other data the controller has allocated in viewDidUnload (mostly called when there's a memory warning - it's a 3.0 only thing though).
As noted mostly view controllers will be deallocated when you leave them (hit back) so there aren't generally that any hanging around anyway. But sometimes I find it handy to leave a reference around if I want to re-open that view in the same state the user left it (does not work between app launches).