Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid deallocated objects being accessed in callbacks/etc?

The issue has been discussed here and here, but I wonder if there is a more solid way to solve this whether you have delegates or not - when a function is called after a delay. At a certain point in a program, at a button push, an object - a CCLayer - is created. That layer creates several objects, some of them at callbacks. That created object layer has a "back" button which destroys it. I am running into a problem when the callbacks, etc are triggered AFTER that object is destructed and try to access objects that don't exist anymore - where the "message sent to deallocated instance 0x258ba480" gives me this good news. How do I avoid that?

1) Is there a way to kill the callbacks (because I obviously don't need them anymore) 2) should/can I test for the existence of these possibly non-existent objects at the callbacks themselves 3) something else?

(My callback is code for checking for an internet connection that I copied from this illustrious website - may it live long and prosper-, using Reachability, and I could solve the problem by simply moving it to the main view and setting a flag on the child view, but I don't want to.)

- (void)testInternetConnection
{
    internetReachableFoo = [Reachability reachabilityWithHostname:@"www.google.com"];

    // Internet is reachable
    internetReachableFoo.reachableBlock = ^(Reachability*reach)
    {
         // Update the UI on the main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Yayyy, we have the interwebs!");
            //I do the net stuff here
    });
};

// Internet is not reachable
internetReachableFoo.unreachableBlock = ^(Reachability*reach)
{
    // Update the UI on the main thread
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"Someone broke the internet :(");
        noNetMessageLabel.visible=true; //<-------this goes kaboom
        noNetFlag=true;

    });
};

[internetReachableFoo startNotifier];

}

like image 497
Yazan alhoroub Avatar asked Oct 01 '13 13:10

Yazan alhoroub


2 Answers

There are basically two ways to avoid deallocated delegates from being messaged:

  1. Hold onto the objects you want to message later. That way they won’t get deallocated. This is the case with block callbacks – if a block references some object, the object gets retained until the block ceases to exist. If you message some objects from a block and hit a deallocated object, you must have screwed up the memory management somewhere.

  2. Clear the delegation link before you release the delegate. Nowadays this is usually done using weak, zeroing properties that are automatically set to nil when the referenced object is deallocated. Very convenient. Not your case.

like image 86
zoul Avatar answered Sep 27 '22 18:09

zoul


You might consider several options:

First, you may just check for existence of an object before passing message to it:

if (noNetMessageLabel)
  noNetMessageLabel.visible = true;

But personally I consider that as a bad architecture.

More wise decision, from my point of view, would be move the code of displaying any alert regarding internet connectivity to the model. Create method like this in AppDelegate or in the model:

- (NSError*)presentConnectivityAlert
{
   if () //any error condition checking appropriate
       [[NSNotificationCenter defaultCenter]
          postNotificationName:@"connectivityAlert"
          object:self
          userInfo:userInfo];
}

Also you may consider moving internet checking code to the model too.

In the ViewControllers of your app implement listening to this notification.

- (void)viewDidLoad {
    [[NSNotificationCenter defaultCenter] 
      addObserver:self
      selector:@selector(didReceiveRemoteNotification:)                                                  
      name:@"connectivityAlert"
      object:nil];
}

- (void)viewDidUnload {
[[NSNotificationCenter defaultCenter] 
  removeObserver:self
  name:@"connectivityAlert"
  object:nil];
}

-(void)didReceiveRemoteNotification:(NSDictionary *)userInfo {
   if (self.isViewLoaded && self.view.window) {
      //Present the user with alert
   }
}

Thus you have more general and quite versatile approach to handle connectivity issues throughout all your application.

Is there a way to kill the callbacks

It's not possible to cancel block (in your case), but it's possible to cancel NSOperation in NSOperationQueue. But that will require to rewrite your implementation of Reachability.

like image 24
Artem Abramov Avatar answered Sep 27 '22 18:09

Artem Abramov