22
votes

Background:

All my OpenTok methods are in one ViewController that gets pushed into view, like a typical Master/detail VC relationship. The detailVC connects you to a different room depending on your selection. When I press the back button to pop the view away, I get a crash (maybe 1 out of 7 times):

[OTMessenger setRumorPingForeground] message sent to deallocated instance xxxxx

or

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

I put my unpublish/disconnect methods in 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];
}

Here is a trace:

Image

Here is the DetailViewController on Github: link here

How to reproduce:

  1. Make a selection from the MasterVC, that takes you into the DetailVC which immediately attempts to connect to a session and publish

  2. Go back to previous, MasterVC quickly, usually before the session has had an a chance to publish a stream

  3. Try this several times and eventually it will crash.

  4. If I slow down and allow the publisher a chance to connect and publish, it is less likely to cause a crash.

Expected result:

It should just disconnect from the session/unpublish and start a new session as I go back and forth between the Master/DetailVC's.

Other:

What is your device and OS version? iOS 6

What type of connectivity were you on? wifi

Zombies Enabled? Yes

ARC Enabled? Yes

Delegates set to nil? Yes, as far as I know

Any help solving this crash would be greatly appreciated. Perhaps I'm missing something basic that I just can't see.

What seems to happen is that the OTSession object in the OpenTok library continues to to send messages to objects in that library that have since been deallocated by switching views. The library has a [session disconnect] method that works fine if you give it enough time, but it takes close to 2-3 seconds, and that's a long time to pause an app between views.

This might be a stupid question, but: Is there anyway to stop all processes initiated by a certain VC?

7
Zombies should be disabled, you can only use this option if you are checking if there zombies in your code. Once you activated that option objects will never be releasedAndrea
@TIMEX the Git repository throws 404Burhanuddin Sunelwala
@Emin Israfil the link to git repo is not available. Are you still looking for an answer for this?Burhanuddin Sunelwala
Your github link doesn't work. Can you give us another way to see your code? Also: Where are you calling setRumorPingForeground? Where are you calling setSessionConnectionStatus?Pauls
I can't look at your full code, as mentioned above the github link doesn't work. But, it seems like you shouldn't really have the management of your session, publisher and subscriber in the view at all. Perhaps creating a singleton pattern that would hold it until the exchange has happened would be better. Or, in an object you store elsewhere if you need multiples to exist at the same time.SefTarbell

7 Answers

4
votes

Closing the session from viewWillDisappear() works if you can determine for sure that the view is going to be popped, not pushed or hidden. Some answers suggest putting this code in dealloc(). Regarding those suggestions, Apple says,

You should try to avoid managing the lifetime of limited resources using dealloc.

So, here is how you can determine for sure that your view will get popped. viewWillDisappear() is called when the view is popped from the stack, or is otherwise pushed somewhere else. This is the easiest way to determine which, and then unpublish/disconnect if it is truly popped. You can test for this with isMovingFromParentViewController. Also, here is where you can remove specific observers.

- (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");
    }
}

Ref: Testing for Specific Kinds of View Transitions

3
votes

Looks like OpenTok have a bug with usage NSNotificationCenter inside of OTSession and OTMessenger classes. You can see these classes in call-stack are separated with NSNotificationCenter calls:

enter image description here

You can manually unsubscribe your OTSession object when dealloc (hope OpenTok uses defaultCenter):

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

You need to check if this code (dealloc) is really executed. If not - you need to fix problem of UIViewController deallocation. A lot of other answers contains tips how to help UIViewController to be deallocated.

2
votes

-(void)viewDidDisappear:(BOOL)animated is called whenever the view is hidden, not only when it is popped from the view stack.

So if you push a view over it, viewWillDisappear will be called and your objects deleted.

This is specially problematic if you load these same objects from viewDidLoad: instead of viewDidAppear:.

Perhaps you should put your unpublish/disconnect code in -(void)dealloc.

2
votes

This is what Apple suggests:

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

But this is only the last resort to remove observers, still often a good habit to always add it to make sure everything is cleand up on dealloc to prevent crashes.

It's still a good idea to remove the observer as soon as the object is no longer ready (or required) to receive notifications.

1
votes

I most of the time put such a code in the viewWillDisappear, but I guess that doesn't really matter.

I believe the issue is that your session delegate is not set to nil. Just add the following in your viewDidDisappear:

self.session.delegate=nil;
1
votes

You must call [super viewDidDisappear:animate]; at the beginning. May be it will fix your issue. And better cleanup your session and subscriber in dealloc method:

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

According to the stack trace you have posted, the notification center reaches out to an OTSession instance that is still alive. Afterwards, this instance provokes a crash calling methods on deallocated objects. Adding to that the two different deallocated instance messages, we know there are asynchronous events occuring after the death of some objects that trigger the random crash you are having.

As ggfela suggested, you should make sure to nil out the delegates you have connected to the OpenTok framework. I strongly suggest you do that in the dealloc method as we want to make sure that after that point, no one has any dangling references to your object :

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

Another odd thing in the code is that your handler for sessionDidConnect: creates a new dispatch_queue every time it is being called in order to call doPublish:. This means that you have concurrent threads sharing the SROpenTokVideoHandler instance which makes it prone to race conditions.