Message sent to a deallocated instance
Asked Answered
Y

7

22

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?

Yoshieyoshiko answered 14/8, 2013 at 4:52 Comment(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 releasedPerrine
@TIMEX the Git repository throws 404Celebrate
@Emin Israfil the link to git repo is not available. Are you still looking for an answer for this?Celebrate
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?Stockholder
Oh KeyValueObservation <3Generality
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.Alcmene
@Emin Israfil - Q) Is there anyway to stop all processes initiated by a certain VC ? A) you could move all the processes inside a singleton class and deal with it .Opportunity
D
4

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

Dermal answered 18/4, 2015 at 1:44 Comment(0)
L
3

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.

Luxor answered 18/4, 2015 at 0:21 Comment(0)
D
2

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

Duma answered 14/4, 2015 at 19:58 Comment(1)
It's not wise to put timely disconnect code in the dealloc method. That section is for deallocating the memory occupied by the receiver. viewDidDisappear is a better choice with a test if the view was popped or not. If so, then disconnect.Dermal
A
2

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.

Arlenarlena answered 16/4, 2015 at 20:30 Comment(3)
For iOS 5 & below we have to call [super dealloc]; at the end of dealloc since there is no ARC on those versionsOpportunity
You're right, but with ARC you may not call [super dealloc] - and I doubt many people do still support <=iOS5 ...Arlenarlena
Just a thought ! We have to prepared for this !Opportunity
D
1

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;
Driedup answered 14/8, 2013 at 8:31 Comment(2)
Thanks for the advice, but that does not fix it. My edit might help.Yoshieyoshiko
When a session disconnects, all OTSubscriber and OTPublisher objects' views are removed from their superviews. Have you tried to only do a session disconnect? Don't put the subscriber on nil since it seems the session needs it.Driedup
S
1

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
}
Secant answered 14/4, 2015 at 18:48 Comment(0)
S
1

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.

Seiler answered 18/4, 2015 at 6:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.