被释放实例接收到的消息

22

背景:

我所有的OpenTok方法都在一个ViewController中,该视图控制器被推入视图中,就像典型的主/细节VC关系一样。详细VC会根据您的选择将您连接到不同的房间。当我按下返回按钮以弹出视图时,可能会发生崩溃(大约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];
}

这里是一个跟踪:

Image

这里是Github上的DetailViewController: 链接在这里

如何复现:

  1. 从MasterVC中进行选择,进入DetailVC,该VC立即尝试连接到一个会话并发布。

  2. 迅速返回到之前的MasterVC,通常在该会话有机会发布流之前。

  3. 尝试重复几次然后它就会崩溃。

  4. 如果我放慢速度,让发布者有时间连接和发布,那么它就不太可能导致崩溃。

期望结果:

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

其他信息:

你的设备和操作系统版本是什么? iOS 6

你使用的是哪种类型的网络连接? wifi

Zombies已启用吗? 是的

ARC已启用吗? 是的

委托已设置为空吗? 是的,据我所知

如果您能帮助解决此崩溃问题,将不胜感激。也许我错过了一些基本的东西,看不到。

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

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


1
僵尸对象应该被禁用,只有在检查代码中是否存在僵尸对象时才能使用此选项。一旦激活了该选项,对象将永远不会被释放。 - Andrea
2
@TIMEX Git仓库返回404错误。 - Burhanuddin Sunelwala
1
@Emin Israfil,git仓库的链接不可用。您还在寻找答案吗? - Burhanuddin Sunelwala
1
你的 GitHub 链接无法访问。你能提供另一种查看你代码的方式吗?还有:你在哪里调用 setRumorPingForeground?你在哪里调用 setSessionConnectionStatus? - Pauls
1
我无法查看您的完整代码,如上所述,github链接无效。但是,似乎您不应该在视图中管理会话、发布者和订阅者。也许创建一个单例模式来保存它,直到交换发生会更好。或者,在其他地方存储对象,如果需要同时存在多个对象。 - SefTarbell
显示剩余2条评论
7个回答

4
viewWillDisappear()关闭会话在确定视图将要被弹出而不是推送或隐藏时是有效的。有些答案建议把这段代码放在dealloc()中。关于这些建议,苹果

你应该尽量避免使用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");
    }
}

参考文献:测试特定种类的视图转换


3
看起来OpenTokOTSessionOTMessenger类中使用NSNotificationCenter存在一个bug。你可以看到这些类在调用堆栈中与NSNotificationCenter的调用是分开的:

enter image description here

当dealloc时,你可以手动取消订阅你的OTSession对象(希望OpenTok使用defaultCenter):
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self.session];
}

您需要检查是否真正执行了此代码 (dealloc)。如果没有执行,您需要修复 UIViewController 的释放问题。许多其他答案包含了有助于释放 UIViewController 的提示。


2

这是苹果建议的做法:

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

但这只是移除观察者的最后手段,通常最好养成一直添加它的好习惯,确保在dealloc时清理所有内容以防止崩溃。

当对象不再需要接收通知时,尽快删除观察者仍然是个好主意,详见Objective-C:在哪里为NSNotification移除观察者?


对于 iOS 5 及以下版本,由于这些版本没有 ARC,因此我们必须在 dealloc 结束时调用 [super dealloc]; - Durai Amuthan.H
你是正确的,但使用自动引用计数(ARC)时,您可能无法调用[super dealloc] - 我怀疑很少有人仍然支持<=iOS5 ... - dogsgod
只是一个想法!我们必须为此做好准备! - Durai Amuthan.H

2

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

如果你把一个视图推到上面,viewWillDisappear将被调用,你的对象将被删除。

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

也许你应该把你的未发布/断开连接的代码放在-(void)dealloc中。


在dealloc方法中放置及时断开连接代码是不明智的。该部分用于释放接收器占用的内存。viewDidDisappear是更好的选择,可以测试视图是否被弹出。如果是,则断开连接。 - Drakes

1

你需要在开头调用 [super viewDidDisappear:animate];,这样可能会解决你的问题。同时最好在 dealloc 方法中清理你的 session 和 subscriber:

- (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
}

1
根据您发布的堆栈跟踪,通知中心会访问仍然存在的OTSession实例。随后,该实例调用已释放对象上的方法引起崩溃。
此外,由于两个不同的已释放实例消息,我们知道在某些对象死亡后,异步事件触发了导致您遇到的随机崩溃。
正如ggfela建议的那样,您应确保将连接到OpenTok框架的委托设置为nil。我强烈建议您在dealloc方法中执行此操作,因为我们希望在此之后,没有人有任何指向您的对象的悬空引用:
- (oneway void)dealloc
{
    self.session.delegate = nil;
    self.publisher.delegate = nil;
    self.subscriber.delegate = nil;
}

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

1

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

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

self.session.delegate=nil;

谢谢你的建议,但那并不能解决问题。我的修改可能会有所帮助。 - Emin Israfil
当一个会话断开连接时,所有OTSubscriber和OTPublisher对象的视图都将从它们的父视图中移除。您尝试过仅执行会话断开操作吗?不要将订阅者设置为nil,因为似乎会话需要它。 - ggfela

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接