消息发送到解除分配的实例

时间:2013-08-14 04:52:44

标签: ios objective-c memory-management xcode4 opentok

背景

所有我的OpenTok方法都在一个ViewController中,它被推入视图,就像典型的Master / detail 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];
}

这是一个跟踪:

Image

这是Github上的DetailViewController:link here

如何重现:

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

  2. 通常在会话有机会发布流之前快速回到之前的MasterVC

  3. 尝试几次,最终会崩溃。

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

  5. 预期结果:

    它应该断开会话/取消发布并开始一个新的会话,因为我在Master / DetailVC之间往返。

    其他

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

    您使用什么类型的连接? 无线网络

    Zombies已启用? 是

    ARC已启用? 是

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

    任何帮助解决这次崩溃的人都将不胜感激。也许我错过了一些我根本无法看到的基本知识。

    似乎发生的事情是OpenTok库中的OTSession对象继续向该库中的对象发送消息,这些对象已经被切换视图解除分配。该库有一个[会话断开]方法,如果你给它足够的时间,它可以正常工作,但它需要接近2-3秒,这是很长一段时间在视图之间暂停一个应用程序。

    这可能是一个愚蠢的问题,但是: 反正有没有停止某个VC发起的所有进程?

7 个答案:

答案 0 :(得分:4)

如果您可以确定视图将被弹出,而不是被推送或隐藏,则从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

答案 1 :(得分:3)

OpenTokNSNotificationCenter类中,OTSession似乎有使用OTMessenger的错误。您可以看到调用堆栈中的这些类与NSNotificationCenter调用分开:

enter image description here

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

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

您需要检查此代码(dealloc)是否真正执行。如果不是 - 您需要解决UIViewController解除分配的问题。许多其他答案包含如何帮助UIViewController解除分配的提示。

答案 2 :(得分:2)

只要视图被隐藏,就会调用

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

因此,如果您在其上推送视图,则会调用viewWillDisappear并删除您的对象。

如果您从viewDidLoad:而不是viewDidAppear:加载这些相同的对象,则会出现此问题。

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

答案 3 :(得分:2)

这就是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仍然是一个好主意。

答案 4 :(得分:1)

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

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

self.session.delegate=nil;

答案 5 :(得分:1)

你必须调用[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
}

答案 6 :(得分:1)

根据您发布的堆栈跟踪,通知中心会联系到仍处于活动状态的OTSession实例。之后,这个实例会在解除分配的对象上引发崩溃调用方法。 再加上两个不同的解除分配的实例消息,我们知道在某些对象死亡后会发生异步事件,触发您遇到的随机崩溃。

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

- (oneway void)dealloc
{
    self.session.delegate = nil;
    self.publisher.delegate = nil;
    self.subscriber.delegate = nil;
}

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