发送到已释放实例的消息

Posted

技术标签:

【中文标题】发送到已释放实例的消息【英文标题】:Message sent to a deallocated instance 【发布时间】:2013-08-15 22:01:35 【问题描述】:

背景:

我所有的 OpenTok 方法都在一个 ViewController 中,它会被推送到视图中,就像典型的主/从 VC 关系。 detailVC 根据您的选择将您连接到不同的房间。当我按下后退按钮以弹出视图时,我遇到了崩溃(可能 7 次中有 1 次):

[OTMessenger setRumorPingForeground] message sent to deallocated instance xxxxx

[OTSession setSessionConnectionStatus:]: message sent to deallocated instance 0x1e1ee440

我将我的取消发布/断开连接方法放在 viewDidDisappear 中:

-(void)viewDidDisappear:(BOOL)animated

    //dispatch_async(self.opentokQueue, ^
    [self.session removeObserver:self forKeyPath:@"connectionCount"];

    if(self.subscriber)
        [self.subscriber close];
        self.subscriber = nil;
    

    if (self.publisher) 
        [self doUnpublish];
    

    if (self.session) 
        [self.session disconnect];
        self.session = nil;
    
    //);
    [self doCloseRoomId:self.room.roomId position:self.room.position];

这是一个痕迹:

这是 Github 上的 DetailViewController:link here

如何重现:

    从 MasterVC 中进行选择,这会将您带入 DetailVC,它会立即尝试连接到会话并发布

    快速返回上一个,MasterVC,通常在会话有机会发布流之前

    多试几次,最终会崩溃。

    如果我放慢速度并让发布者有机会连接和发布,则不太可能导致崩溃。

预期结果:

当我在 Master/DetailVC 之间来回切换时,它应该只是断开会话/取消发布并开始一个新会话。

其他:

您的设备和操作系统版本是什么? ios 6

您使用的是哪种类型的连接? 无线网络

僵尸启用? 是的

ARC 已启用? 是的

代表设置为零? 是的,据我所知

任何解决此崩溃的帮助将不胜感激。也许我错过了一些我看不到的基本内容。

似乎发生的情况是 OpenTok 库中的 OTSession 对象继续向该库中的对象发送消息,这些对象已通过切换视图释放。该库有一个 [session disconnect] 方法,如果你给它足够的时间,它可以正常工作,但它需要接近 2-3 秒,而且在视图之间暂停应用程序的时间很长。

这可能是一个愚蠢的问题,但是: 有没有办法停止某个VC启动的所有进程?

【问题讨论】:

僵尸应该被禁用,如果你检查你的代码中是否有僵尸,你只能使用这个选项。一旦您激活该选项对象将永远不会被释放 @TIMEX Git 存储库抛出 404 @Emin Israfil git repo 的链接不可用。您还在为此寻找答案吗? 您的 github 链接无效。你能给我们另一种方式来查看你的代码吗?另外:你在哪里调用 setRumorPingForeground?你在哪里调用 setSessionConnectionStatus? 我无法查看您的完整代码,如上所述,github 链接不起作用。但是,您似乎根本不应该在视图中管理会话、发布者和订阅者。也许创建一个在交换发生之前保持它的单例模式会更好。或者,如果您需要多个同时存在,则存储在其他地方的对象中。 【参考方案1】:

如果您可以确定视图将被弹出,而不是推送或隐藏,则从viewWillDisappear() 关闭会话有效。一些答案建议将此代码放在dealloc() 中。关于这些建议,Apple says,

您应该尝试避免使用 dealloc 来管理有限资源的生命周期。

因此,您可以通过以下方式确定您的视图是否会弹出。 viewWillDisappear() 在视图从堆栈中弹出时被调用,或者被推送到其他地方。这是确定哪个最简单的方法,如果它真的被弹出,则取消发布/断开连接。您可以使用isMovingFromParentViewController 对此进行测试。此外,您可以在此处删除特定的观察者。

- (void)viewWillDisappear:(BOOL)animated 
    [super viewWillDisappear:animated]

    // This is true if the view controller is popped
    if ([self isMovingFromParentViewController])
    
        NSLog(@"View controller was popped");

        // Remove observer
        [[NSNotificationCenter defaultCenter] removeObserver:self.session];

        ...

        //dispatch_async(self.opentokQueue, ^
            if(self.subscriber)
                [self.subscriber close];
                self.subscriber = nil;
            

            if (self.publisher) 
                [self doUnpublish];
            

            if (self.session) 
                [self.session disconnect];
                self.session = nil;
            
            //);
            [self doCloseRoomId:self.room.roomId position:self.room.position];
    
    else
    
        NSLog(@"New view controller was pushed");
    

参考:Testing for Specific Kinds of View Transitions

【讨论】:

【参考方案2】:

看起来OpenTokOTSessionOTMessenger 类中使用NSNotificationCenter 存在错误。您可以看到调用堆栈中的这些类与 NSNotificationCenter 调用分开:

你可以在dealloc时手动取消订阅OTSession对象(希望OpenTok使用defaultCenter):

- (void)dealloc 
    [[NSNotificationCenter defaultCenter] removeObserver:self.session];

你需要检查这段代码(dealloc)是否真的被执行了。如果不是 - 您需要修复 UIViewController 释放的问题。许多其他答案包含如何帮助 UIViewController 被释放的提示。

【讨论】:

【参考方案3】:

根据您发布的堆栈跟踪,通知中心会联系到一个仍然存在的 OTSession 实例。之后,此实例会引发对已释放对象的崩溃调用方法。 再加上两条不同的已释放实例消息,我们知道在某些对象死亡后会发生异步事件,这些事件会触发您遇到的随机崩溃。

正如 ggfela 建议的那样,您应该确保将已连接到 OpenTok 框架的代表清零。我强烈建议你在 dealloc 方法中这样做,因为我们要确保在那之后,没有人对你的对象有任何悬空引用:

- (oneway void)dealloc

    self.session.delegate = nil;
    self.publisher.delegate = nil;
    self.subscriber.delegate = nil;

代码中的另一个奇怪之处是,您的 sessionDidConnect: 处理程序在每次被调用时都会创建一个新的 dispatch_queue,以便调用 doPublish:。这意味着您有并发线程共享 SROpenTokVideoHandler 实例,这使其容易出现竞争条件。

【讨论】:

【参考方案4】:

这就是Apple suggests:

-(void) dealloc 
      [[NSNotificationCenter defaultCenter] removeObserver:self];

但这只是删除观察者的最后手段,始终添加它以确保在 dealloc 上清理所有内容以防止崩溃通常仍然是一个好习惯。

remove the observer as soon as the object is no longer ready (or required) to receive notifications 还是个好主意。

【讨论】:

对于 iOS 5 及以下版本,我们必须在 dealloc 末尾调用 [super dealloc];,因为这些版本没有 ARC 你是对的,但是使用 ARC 你可能不会调用 [super dealloc] - 我怀疑很多人仍然支持 只是一个想法!我们必须为此做好准备!【参考方案5】:

-(void)viewDidDisappear:(BOOL)animated 会在视图被隐藏时调用,而不仅仅是在视图堆栈中弹出时调用。

因此,如果您将视图推送到它上面,viewWillDisappear 将被调用并删除您的对象。

如果您从 viewDidLoad: 而不是 viewDidAppear: 加载这些相同的对象,这会特别成问题。

也许您应该将取消发布/断开连接代码放在-(void)dealloc

【讨论】:

在dealloc方法中放入及时断开代码是不明智的。该部分用于释放接收器占用的内存。 viewDidDisappear 是一个更好的选择,可以测试视图是否弹出。如果是,则断开连接。【参考方案6】:

你必须调用 [super viewDidDisappear:animate];一开始。也许它会解决你的问题。 并在 dealloc 方法中更好地清理您的会话和订阅者:

- (void) dealloc 
    [self.session removeObserver:self forKeyPath:@"connectionCount"];

    if(self.subscriber)
        [self.subscriber close];
        self.subscriber = nil;
    

    if (self.publisher) 
        [self doUnpublish];
    

    if (self.session) 
        [self.session disconnect];
        self.session = nil;
    
    [self doCloseRoomId:self.room.roomId position:self.room.position];

  //[super dealloc]; //for non-ARC

【讨论】:

【参考方案7】:

我大部分时间都将这样的代码放在 viewWillDisappear 中,但我想这并不重要。

我认为问题在于您的会话委托未设置为零。只需在 viewDidDisappear 中添加以下内容:

self.session.delegate=nil;

【讨论】:

感谢您的建议,但这并不能解决问题。我的编辑可能会有所帮助。 当会话断开连接时,所有 OTSubscriber 和 OTPublisher 对象的视图都将从其父视图中删除。您是否尝试过仅断开会话连接?不要将订阅者设为 nil,因为会话似乎需要它。

以上是关于发送到已释放实例的消息的主要内容,如果未能解决你的问题,请参考以下文章

发送到已释放实例问题的消息

[UINavigationController 保留]:发送到已释放实例的消息

发送到已释放实例的消息

tableView:didSelectRowAtIndexPath - 发送到已释放实例的消息

发送到已释放实例 Core Data 的消息

[NSDecimalNumber 保留]:消息发送到已释放实例 0x174222220,但为啥呢?