Search code examples
iosreachabilityappdelegatexcode-instrumentsmemory-management

Multiple instances of ViewController being called simultaneously cause the app to crash in AppDelegate


What am I doing ?

  1. I am currently crash-proofing my app in absence of internet.
  2. I have installed Little snitch to diable net access for time being.
  3. When the request is made and if the response was nil, "Reachability test" is triggered. ReachabilityController in the code below is just a wrapper for Reachability class provided by Apple.

Does this code work in other places ?

The application is full of online requests and I have never had this problem in other view controllers. If the internet is not available, it shows a nice little message describing the state.

Where does the error occur

App Delegate

Error

* * * -[ReachabilityController respondsToSelector:]: message sent to deallocated instance 0x12a71320

The ReachabilityController shows a UIAlertView describing the state of error. The Alert view appears for abt a second and then the app crashes.

Debuging and Instruments ?

The NSLog(@"%p", reachability); logs same adress as in the error. 0x12a71320. It goes without saying that this adress varies on every run.

The Zombies template in Instruments also points out at Reachability Controller.

The white patch for 4 rows in "Responsible" section in the image below contains name of the App.

Zombie object

What I am expecting ?

Honestly, I don't have a clue as in the reason behind this behavior. I am sure it has to be very basic.

Do I have to declare a property to retain "ReachabilityController" as compared to local declaration now ?

Code

The fragment where crash occurs.

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);

dispatch_async(queue, ^{
    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://myUrl"]];
    NSData *authResponse = [NSData dataWithContentsOfURL:url];

    dispatch_sync(dispatch_get_main_queue(), ^{
        if(authResponse == nil) {
            ReachabilityController *reachability = [[ReachabilityController alloc] init];
            NSLog(@"%p", reachability);
            [reachability checkReachability];
            NSLog(@"%p", reachability);
            return; // app crashes round here
        } 
}

ReachabilityController.h

#import <UIKit/UIKit.h>
#import "Reachability.h"

@interface ReachabilityController : UIViewController
  -(void) checkReachability ;
@end

ReachabilityController.m

#import "ReachabilityController.h"

@interface ReachabilityController ()

@end

@implementation ReachabilityController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
   self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
   if (self) {
     // Custom initialization
   }
   return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
// Do any additional setup after loading the view.
}

- (void)viewDidUnload
{
    [super viewDidUnload];

}


-(void) checkReachability {
    Reachability* internetAvailable = [Reachability reachabilityForInternetConnection];
    NetworkStatus netStatus = [internetAvailable currentReachabilityStatus];
    NSString *messageText;
    if (netStatus == NotReachable)
    {
        messageText = [NSString stringWithFormat:@"iRestaurant has determined that internet access is not available at the moment"];

    } else {
        NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
        NSString *ipAdress = [infoDict objectForKey:@"LookUpIpAdress"];
        NSString *host = [NSString stringWithFormat:@"%@/iRestaurant", ipAdress];

        Reachability *netReach = [Reachability reachabilityWithHostName:host];
        NetworkStatus hostStatus = [netReach currentReachabilityStatus];
        if (hostStatus == NotReachable)
        {
            messageText = [NSString stringWithFormat:@"Sorry, for your inconvenience. iRestaurant server is currently unreachable. The iRestaurant server may be down for maintenance. Please check back after 5 mins"];

        } else {
            messageText = [NSString stringWithFormat:@"Sorry, for your inconvenience. This service is temporarily unavailable. We are working very hard to get it back on track."];
        }

    }

    UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"iRestaurant"
                                                    message:messageText
                                                   delegate:self
                                          cancelButtonTitle:@"Done"
                                          otherButtonTitles:nil];
    [alertView show];

}


@end

UPDATE:

change question's title

Just a recent discovery. There are 3 instances where a data is requested from online server. As the internet is not available, all three methods almost request Reachability test at the same time. All three methods have method declaration something similar to the one above. All three methods use "local declaration" shown below to trigger the test.

ReachabilityController *reachability = [ReachabilityController alloc] init];
[reachability checkReachability];

Solution

  • You set the delegate for the UIAlertView to self. Unless you have a way to retain your ReachabilityController until the UIAlertView is released, this is a dangling reference.

    Do you need the UIAlertView to interact with your ReachabilityController? It might be simplest to just pass nil as the delegate.