Is the ARC Memory Management of the current ViewController: animated: completion: broken in iOS 6.x?

My view controller presents the view through the presentViewController:animated:completion: method. The view is represented by a fine.

Then I reject this view and resubmit it and get the following failure:

 *** -[WebBrowser isKindOfClass:]: message sent to deallocated instance 0x1f640ac0 

My code uses ARC. Here is the code for my WebBrowser class, a simple built-in browser.

WebBrowser.h:

 @interface WebBrowser : ITViewController <UIWebViewDelegate, UIAlertViewDelegate> @property (nonatomic, strong) NSString *URL; @property (nonatomic, weak) IBOutlet UIWebView *webView; @property (nonatomic, weak) IBOutlet UIActivityIndicatorView *spinner; - (id)initWithURL:(NSString *)URL; - (IBAction)dismissView:(id)sender; @end 

WebBrowser.m:

 @implementation WebBrowser - (id)initWithURL:(NSString *)URL_ { self = [super initWithNibName:@"MyNib" bundle:nil]; if (self) { self.URL = URL_; } return self; } - (void)viewDidLoad { [super viewDidLoad]; self.webView.delegate = self; if (self.URL) { [self.webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.URL]]]; } } - (IBAction)dismissView:(id)sender { self.URL = nil; [self.webView stopLoading]; self.webView.delegate = nil; [self dismissViewControllerAnimated:YES completion:NULL]; } // + some non-related web view delegate stuff @end 

And finally, this is how I present the view in my parent view controller:

 WebBrowser *browser = [[WebBrowser alloc] initWithURL:URL]; [self presentViewController:browser animated:YES completion:NULL]; 

I am running iOS 6 and compiling using ARC.

At first, I thought this error was related to ARC. Here is my original post:

ORIGINAL MAIL

I noticed crashes in my application with iOS 6.x when displaying modal view controllers and release when it worked perfectly with previous versions of iOS.

Kill me for not using ARC yet (this is my next big step in this project), but, for example, when displaying leaders in Game Center with the following code, follow these steps:

  • display leaderboard
  • close leaderboard
  • display the leaderboard again (UPDATE ACCURACY: by running the same showLeaderboard shown below, i.e. displaying a new instance of GKLeaderboardViewController )

then the following error will occur

 *** -[GKLeaderboardViewController isKindOfClass:]: message sent to deallocated instance 0x17467120 

This is my code:

 - (void)showLeaderboard { if ([[GKLocalPlayer localPlayer] isAuthenticated]) { GKLeaderboardViewController *lb = [[GKLeaderboardViewController alloc] init]; lb.category = ...; lb.leaderboardDelegate = self; self.modalPresentationStyle = UIModalPresentationCurrentContext; [self presentModalViewController:lb animated:YES]; [lb release]; } else { ... } } - (void)leaderboardViewControllerDidFinish:(GKLeaderboardViewController *)viewController{ [self dismissModalViewControllerAnimated:YES]; } 

It turns out that deleting the [lb release] command solves my problem and, again, that with iOS 5.x there is no such crash.

The same thing happens with the Game Center achievements view controller or with any other custom view controllers that are displayed using presentModalViewController:

It also seems that replacing the deprecated- presentModalViewController: instruction presentModalViewController: new presentViewController:animated:completion: DOES NOT SOLVE THE FAILURE problem.

+8
ios ios5 ios6 modalviewcontroller
source share
2 answers

I see at least one possible problem:

 [self.webView stopLoading]; self.webView.delegate = nil; 

In general, it is safer to set delegate to nil before calling stopLoading .

In addition, it is definitely safer to use dismissViewControllerAnimated way it is supposed to be used, i.e. call it on the presenting controller. Although the documentation states that the call is passed to the view controller, it is not recommended to call the method on an object that is freed inside the method.

 - (IBAction)dismissView:(id)sender { // pass the event to the presenting controller // the presenting controller should call [self dismissViewControllerAnimated:YES completion:NULL]; } - (void)viewWillDissapear { [super viewWillDissapear]; //no need to do this, it done automatically in ARC //self.URL = nil; self.webView.delegate = nil; [self.webView stopLoading]; } 
+1
source share

Edit

According to Apple's documentation, you SHOULD discard the viewcontroller from the parent (view) viewcontroller: http://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html#//apple_ref/doc/uid7TP445 CH111-SW14

β€œWhen it comes time to reject the presented view controller, the preferred approach is to allow the view manager to reject it. In other words, when possible, the same view controller that the view controller introduced should also take responsibility for dismissing this.” .

Also, can you clarify which upcoming controllers represent which and how much overlap. from apple docs:

"If you consecutively represent multiple view controllers and thus create a stack of presented view controllers, calling this method on the view controller below in the stack rejects its immediate child view controller and all view controllers above that child in the stack. When this happens, only the top the view opens in an animated view, and any intermediate view controllers are simply removed from the stack. using its modal transition style, which may differ from the styles used by other view controllers below on the stack. "

So, if you think this may have anything to do with the delegation methods coming from your web browser, you probably should probably unsubscribe the view from the delegation property / stop viewing the web page from loading in viewWillUnload. and not a retired IBAction, since it won 'must be called.

I edited this to make it more complete / understandable

Before presenting a view, you need to configure your browser view to an instance variable with a strong property. Then set it to zero after rejecting it.

First create a delegate protocol for modals:

 @protocol ModalViewDelegate /** Delegation callback for when a modal has been dismissed */ - (void)modalDidDismiss:(UIViewController*)viewController; @end 

In your view manager interface view, subscribe to the protocol:

 @interface PresentingViewController <ModalViewDelegate> @property (nonatomic,strong) WebBrowser *browserView; @end 

In your implementation, when presenting a view:

 WebBrowser *browser = [[WebBrowser alloc] initWithURL:URL]; browser.delegate = self self.browserView = browser; [self presentViewController:browser animated:YES completion:NULL]; 

In the WebBrowser interface:

 @interface WebBrowser : ITViewController <UIWebViewDelegate, UIAlertViewDelegate> @property (nonatomic, strong) NSString *URL; @property (nonatomic, weak) IBOutlet UIWebView *webView; @property (nonatomic, weak) IBOutlet UIActivityIndicatorView *spinner; @property (nonatomic, weak) id <ModalViewDelegate> delegate; - (id)initWithURL:(NSString *)URL; - (IBAction)dismissView:(id)sender; @end 

In your implementation of WebBrowser:

 - (IBAction)dismissView:(id)sender { self.URL = nil; [self.webView stopLoading]; self.webView.delegate = nil; [self.delegate didDismissBrowser:self]; } 

And again in the parent view controller:

 - (void)didDismissBrowser:(WebBrowser*)browser { if (browser == self.browserView) { [self dismissViewControllerAnimated:YES completion:NULL]; self.browserView = nil; } } 
+1
source share

All Articles