presentViewController:animated:completion: 的 ARC 内存管理是不是在 iOS 6.x 中损坏?

Posted

技术标签:

【中文标题】presentViewController:animated:completion: 的 ARC 内存管理是不是在 iOS 6.x 中损坏?【英文标题】:Is ARC Memory Management of presentViewController:animated:completion: broken in iOS 6.x?presentViewController:animated:completion: 的 ARC 内存管理是否在 iOS 6.x 中损坏? 【发布时间】:2012-12-07 13:44:17 【问题描述】:

我的视图控制器通过presentViewController:animated:completion: 方法呈现视图。视图呈现良好。

然后我关闭这个视图并重新呈现它并得到以下崩溃:

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

我的代码正在使用 ARC。这是我的 WebBrowser 类的代码,一个简单的嵌入式浏览器。

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

最后是我在父视图控制器中呈现视图的方式:

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

我正在运行 ios 6 并使用 ARC 进行编译。

首先我认为这个错误与 ARC 相关。这是我的原帖:

原帖

我注意到我的应用在 iOS 6.x 中显示模态视图控制器时崩溃,而当它在以前版本的 iOS 上运行良好时释放它。

怪我还没有使用 ARC(这是我在这个项目上的下一个重要步骤),但是例如,当使用以下代码显示 Game Center 排行榜时,请执行以下步骤:

    显示排行榜 关闭排行榜 再次显示排行榜(精确更新:通过运行下面所示的相同showLeaderboard,即显示GKLeaderboardViewController实例)

然后,出现以下错误

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

这是我的代码:

- (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];

事实证明,删除 [lb release] 指令可以解决我的问题,而且 iOS 5.x 也不会发生此类崩溃。

Game Center 成就视图控制器或我的任何其他自定义视图控制器也会发生同样的情况,显示为presentModalViewController:

似乎用新的presentViewController:animated:completion: 替换已弃用的-presentModalViewController: 指令也不能解决问题。

【问题讨论】:

导致错误的确切行是什么?添加异常断点。此外,添加一个 NSLog(@"Instance: %p", lb);在 presentModalViewController 行之前,并发布结果(NSLog 的实例地址和导致错误的对象的地址) 如果在完成处理程序中使用presentViewController:animated:completion: 并释放lb 会怎样? ARC 也发生了同样的事情。合规,我不应该这样做。 我的第一条评论(2012 年 12 月:-) 已经有效:添加 NSLog(@"Instance: %p", browser);在 alloc-init 呈现浏览器和另一个在dismiss方法之后。然后,发布结果。您应该有 3 行/实例地址:1-第一个分配的启动浏览器,2-第一个关闭的浏览器,3-第二个分配的浏览器。我怀疑您的超类(ITViewController)在每个 initWithNibName 处为您提供相同的(已解除分配的)实例 好的。但现在问题更清楚了:在你分配初始化新实例之后,某些东西正在向旧实例发送消息。您应该尝试了解是谁 :-) 您是否能够找到导致它的确切代码行? 【参考方案1】:

我看到至少一个可能的问题:

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

一般来说,在调用stopLoading之前将delegate设置为nil会更安全。

此外,以应有的方式使用dismissViewControllerAnimated 肯定更安全,即在presenting 控制器上调用它。尽管文档说明调用已传递给呈现控制器,但在方法内部正在释放的对象上调用方法并不是一个好主意。

- (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's done automatically in ARC
   //self.URL = nil;

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

【讨论】:

谢谢。从逻辑上讲,我切换了指令以使其更有意义。不过这对我的问题没有帮助。 您提出了很好的观点,但这无助于解决我的问题。不过还是谢谢。【参考方案2】:

编辑

根据 Apple 的文档,您应该从父(呈现)视图控制器中关闭视图控制器:http://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ModalViewControllers/ModalViewControllers.html#//apple_ref/doc/uid/TP40007457-CH111-SW14

"当需要关闭呈现的视图控制器时,首选的方法是让呈现的视图控制器解除它。换句话说,只要有可能,呈现视图控制器的同一个视图控制器也应该负责解除它。”

另外,您能否澄清一下哪些视图控制器正在呈现哪些,以及有多少是相互重叠的。来自苹果文档:

“如果你连续呈现多个视图控制器,从而构建了一个呈现视图控制器的堆栈,则在堆栈中较低的视图控制器上调用此方法会解除其直接子视图控制器以及堆栈上该子视图控制器之上的所有视图控制器. 当这种情况发生时,只有最顶层的视图以动画方式被关闭;任何中间视图控制器都被简单地从堆栈中移除。最顶层的视图使用它的模态转换样式被关闭,这可能与使用的样式不同堆栈中较低的其他视图控制器。”

因此,如果您认为它可能与来自您的 webview 的委托方法有关,您可能应该从委托属性中取消订阅视图/停止 webview 在 vi​​ewWillUnload 中加载,而不是在解除 IBAction 中,因为那赢了'不一定会被调用。

我已对其进行了编辑以使其更加完整/清晰

在呈现视图之前,您需要将浏览器视图设置为具有强属性的实例变量。然后将其关闭后设置为零。

首先为 Modals 创建一个委托协议:

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

在您呈现的视图控制器界面中,订阅协议:

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

在您的实现中呈现视图时:

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

在您的 WebBrowser 界面中:

@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

在您的 WebBrowser 实现中:

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

然后回到你的父视图控制器:

- (void)didDismissBrowser:(WebBrowser*)browser

    if (browser == self.browserView)
    
        [self dismissViewControllerAnimated:YES completion:NULL];
        self.browserView = nil;
    

【讨论】:

我相信这会奏效(实际上我在发布我的问题后很不情愿地开始实施此解决方法)但我不喜欢它有两个原因:1/它无助于理解问题所在在我的代码 2/ 因为我不能从我的 WebView 类中解散,这意味着我需要创建一个 WebView 委托协议,并且我的所有客户端视图控制器都需要实现相同的“愚蠢”方法,该方法将关闭视图并重置财产。它不是超级干燥,也不是超级方便。 您最后一次编辑的 webview 代表版本支持我的论点 :) 这是正确解决方案,我刚刚为您的示例使用了一个特定于域的委托协议。如果你担心 DRY(你应该这样做),那么将其设为 ModalViewDelegate 协议,并将方法签名更改为 didDismissModal:(UIViewController*)viewController。您应该利用授权。如果没有该属性,您将拥有一个自动释放的 WebBrowser,它在您的解除调用期间调用了 dealloc,因此是 dealloced 实例。 Apple 的文档支持我的回答:developer.apple.com/library/ios/featuredarticles/… 顺便说一句,这不是一个 ARC 错误,它正在做它应该做的事情。

以上是关于presentViewController:animated:completion: 的 ARC 内存管理是不是在 iOS 6.x 中损坏?的主要内容,如果未能解决你的问题,请参考以下文章