Emanuele Sabetta
Emanuele Sabetta

Reputation: 1611

Strange crash when using the new PageCurl effect reading a PDF with MonoTouch and iOS 5.0

I get a strange crash when using the new PageCurl effect reading a PDF with MonoTouch and iOS 5.0. I've made a simple test case project for MonoDevelop 2.8 and uploaded on GitHub here:

https://github.com/Emasoft/IpaziaPDFReader

It seems that something is getting GCd too early and killing the application, but I can't find what. I've tried to dispose everything in many ways, but in vain. I've already submitted the project tarball to the Xamarin team, but they weren't able to solve the problem.

Is there something broken in the iOS NavigationController memory management? Or am I missing something?

Any help is appreciated, thanks!

UPDATE: I've tried to remove all subviews and sublayers before disposing the objects in all classes, but it still crashing. The only way I found to avoid the crash is to NEVER dispose of the PDF pages, adding them to a List before releasing them, but this is not a viable solution, because in that way memory is consumed rapidly for PDF with many pages and the app crashes anyway when unable to allocate memory for the next page. Another way to avoid the crashes is to dispose of the PDF pages BEFORE turning the pages, forcing the dispose method on the page controller before creating a new page controller, but in this way the current page will become blank and the transition curls an useless empty page. No solution seems to work.

I've updated project on GitHub with the 3 different solutions I've tried (look in the PageDataSource class), you can uncomment them one at time to see the problems.

        //SOLUTION 1   
    void ForcingPageControllerDispose (BookPageController oldPageController)
    {
        // --- IF YOU UNCOMMENT THIS, THE CRASHES GO AWAY, BUT THE PAGE IN THE TRANSITION IS BLANK, SO IS NOT VIABLE
        currentPageController.View.RemoveFromSuperview ();
        currentPageController.Dispose ();
    }


    //SOLUTION 2
    void DisposeThePageControllerWhenDidFinishAnimating (BookPageController oldPageController, UIPageViewController pageViewController)
    {
        // --- IF YOU UNCOMMENT THIS, THE CRASHES STILL HAPPEN
        pageViewController.DidFinishAnimating += delegate(object sender, UIPageViewFinishedAnimationEventArgs e) {
            if (currentPageController != null) {
                currentPageController.View.RemoveFromSuperview ();
                currentPageController.Dispose ();
                Console.WriteLine ("currentPageController disposed for page: " + currentPageController.PageIndex);
            }
        };
    }


    //SOLUTION 3
    void BackupUnusedPagesToAvoidBeingGCd (BookPageController oldPageController)
    {
        // --- IF YOU UNCOMMENT THIS, THE CRASHES GO AWAY, BUT THE PAGES ARE NOT GARBAGE COLLECTED AND AFTER MANY PAGES IPHONE IS OUT OF MEMORY AND IT CRASHES THE APP
        if (parentController.book_page_controllers_reference_list.Contains (currentPageController) == false)
            parentController.book_page_controllers_reference_list.Add (currentPageController);
    }

Upvotes: 3

Views: 488

Answers (2)

miguel.de.icaza
miguel.de.icaza

Reputation: 32694

Since you are taking a dependency on iOS 5 new features, you should also adopt the new View Controller Containment APIs in iOS 5 that solve a few problems with view controllers.

I suggest you check the WWDC Video and slides for for Session 102 "Implement UIViewController Containment".

Upvotes: 1

poupou
poupou

Reputation: 43553

I've already submitted the project tarball to the Xamarin team, but they weren't able to solve the problem.

I'm pretty sure the person assigned to your case will come up with the solution. The bigger the test case the more time it can take.

From a quick view the following, in your AppDelegate.cs, is wrong:

PageTurnViewController viewController = new PageTurnViewController ("PageTurnViewController", null);
window.AddSubview (viewController.View);

since the local viewController instance won't have any reference to it once FinishedLaunching returns and the GC will be able to collect it. However it's needed (on the native side) for keep the View fully valid. This can lead to crashes (there could be other cases too, that's the first and only file I checked this morning).

The solution is to promote the viewController to a field. That will make it alive even when the method returns, making it unavailable to collection.

UPDATE

I had a quick look at your code on github.

  • You are adding (sub)view but you never remove them (when the GC will Dispose them it won't remove them from the super view);

  • You are losing references to views, e.g. in PageDataSource.cs

            newPageController = new BookPageController (nextPageIndex, parentController.currentPDFdocument, parentController);
            return newPageController;
    

After the first page there will already be a reference stored in newPageController which will be overwritten and make the object collectable bug the GC. Since (sub)views are never removed there could still be native reference to them leading to crashes.

For debugging you can add your own finalizers, e.g.

    ~BookPageController ()
    {
        Console.WriteLine ("bu-bye");
    }

and put breakpoints in them. If they get hit, while you think it's still in use, then you likely found a problem.

Upvotes: 1

Related Questions