I had some memory problems due to Xcode's template for a UIPageViewController caching all the page data, so I changed it to load the pages dynamically, so now when my app receives a low memory warning, it releases the memory for page's not showing, but if the user is flipping through the pages real fast by tapping the edge of the screen, it still crashes. I'm guessing this is because it can't free the memory fast enough when didReceiveMemoryWarning gets called. If the user is flipping slowly, it works fine. I limited the speed at which the user can flip pages, but it still happens. I want to be able to free up the memory every time the page is turned, and not have to wait for a low memory warning. I'm using ARC. Is there a way to do this? Or what else can I do to prevent this? Thanks.
EDIT:
(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(SinglePageViewControllerSuperclass *)viewController];
if ((index == 0) || (index == NSNotFound)) {
return nil;
}
index--;
return [self viewControllerAtIndex:index];
}
(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
NSUInteger index = [self indexOfViewController:(SinglePageViewControllerSuperclass *)viewController];
if (index == NSNotFound || index == MAX_PAGE_INDEX) {
return nil;
}
return [self viewControllerAtIndex:++index];
}
I think you hypothesis is correct, since I also experienced a similar behavior: when you flip to the next page, also for the sake of animating things nicely, the new page is allocated before the old one is deallocated and it takes some times for the old one to be deallocated. So, when you flip fast enough, objects are allocated faster than they are deallocated and eventually (actually, pretty soon), your app is killed due to memory usage. The deallocation delay when flipping pages becomes pretty obvious if you follow the allocation/deallocation of memory in Instruments
.
You have three approaches to this, IMO:
implement a "light" viewDidLoad
method (actually, the whole initialization/initial display sequence): in some app, it makes sense, e.g., to load a low resolution image instead of the high resolution image that will be displayed; or, slightly delaying the allocation of additional resources your page need (db access, sound, etc.);
use a pool of pages, say an array of three pages (or 5, it depends on your app), that you keep "reusing" so that the memory profile of your app remains stable and avoid spikes;
careful review the way you allocate and release memory; in this sense, you often read that autorelease add some "inertia" to the release/deallocation mechanism, and this is pretty easy to understand: if you have an autoreleased object, it will be released by its release pool only when you cycle through the main loop (this is true for the main release pool); so, if you have a long sequence of methods that are called when flipping page, this will make the release/dealloc happen later.
There is no magical bullet when it comes to memory usage optimization, it's a pretty detailed and hard work, but IME you will be able to reduce the memory profile of your app if you review your code and apply those 3 guidelines. Especially, inspecting the spikes of memory allocation in Instruments and trying to understand to what they relate is extremely powerful.
Here is an additional change I made, which someone might find helpful:
Basically, I only allow a new page turn to begin if the previous one has finished.
I'm using apple's default PageViewController project as a template so I'll use terms defined in that project.
Whenever a page VC is requested through viewControllerAtIndex:, I set a boolean value on ModelController called 'shouldDenyVC
' to YES
.
In my EbookViewController which is the UIPageViewController's delegate, I capture the gesture recognizers, and assign EbookViewController as their delegate:
self.view.gestureRecognizers = self.pageViewController.gestureRecognizers;
for (UIGestureRecognizer *gr in self.view.gestureRecognizers) {
gr.delegate = self;
}
Then, I'm able to deny a page turn by denying the gesture recognizers:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch: (UITouch *)touch
{
if (_modelController.shouldDenyPageTurn == YES) {
return FALSE;
}
return TRUE;
}
And finally, I set _modelController.shouldDenyPageTurn = NO
at the end of the UIPageViewController delegate method pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
I also had to set _modelController.shouldDenyPageTurn = NO
at the end of any preloading so that page turns are allowed off the bat.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With