Question

I use apple's Reachabiliry class in my non-arc project. And when I run it with instruments to find memory leaks, it referes to Reachability method. Here is the problem:

+ (instancetype)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress;
{
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)hostAddress);

    WReachability* returnValue = NULL;

    if (reachability != NULL)
    {
        returnValue = [[self alloc] init];
        if (returnValue != NULL)
        {
            returnValue->reachabilityRef = reachability;
            returnValue->localWiFiRef = NO;
        }
    }
    return returnValue;
}

The leaked objects are reachability and returnValue. I understand that SCNetworkReachabilityCreateWithAddress creates a new instance and I must CFRelease it, but it happens right in dealloc!

- (void)dealloc
{
    [self stopNotifier];
    if (reachabilityRef != NULL)
    {
        CFRelease(reachabilityRef);
    }
    [super dealloc];
}

So what can I do to avoid memory leak here?

UPD: Maybe the problem is in how reachability get called? I use this method:

+ (instancetype)reachabilityForInternetConnection;
{
    struct sockaddr_in zeroAddress;
    bzero(&zeroAddress, sizeof(zeroAddress));
    zeroAddress.sin_len = sizeof(zeroAddress);
    zeroAddress.sin_family = AF_INET;

    return [self reachabilityWithAddress:&zeroAddress];
}

Then I called Reachability like this:

[[Reachability reachabilityForInternetConnection] startNotifier];

And don't assign it to any object, just use this line. I've tried to change this calls to something like:

Reachability *reachability = [[Reachability reachabilityForInternetConnection] autorelease];
[reachability startNotifier];

But after this analyzer told me "too many autorelease".

Was it helpful?

Solution 3

If returnValue equals to NULL reachability object is leaked, you should release it in this case. Also by Cocoa naming convention (https://developer.apple.com/library/ios/documentation/cocoa/conceptual/MemoryMgmt/Articles/mmRules.html#//apple_ref/doc/uid/20000994-SW1) you must return autoreleased object:

+ (instancetype)reachabilityWithAddress:
{
    ...
     returnValue = [[[self alloc] init] autorelease];

Or rename the method to start for example from new: newReachabilityWithAddress or something like this if you don't want to return an autoreleased object.

Try to run static analyser in Xcode, it can help to spot the problems.

OTHER TIPS

@Alexart answer worked for me but if you want a simplified version, use

+(instancetype)reachabilityWithAddress:(void *)hostAddress
{
   SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
   if (ref)
   {
      id reachability = [[self alloc] initWithReachabilityRef:CFBridgingRetain((__bridge id)ref)];
      CFRelease(ref);
      return reachability;
   }
   return nil;

}

I think better to do it next way:

+ (Reachability*) reachabilityWithHostName: (NSString*) hostName;
{
    Reachability* retVal = NULL;
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(NULL, [hostName UTF8String]);
    if(reachability!= NULL)
    {
        retVal= [[self alloc] init];
        if(retVal!= NULL)
        {
            retVal->reachabilityRef = reachability;
            retVal->localWiFiRef = NO;
        }
        else
        {
            CFRelease(reachability);
        }
    }
    return retVal;
}

Solution with ARC enabled Reachability class.

  1. Add CFAutorelease(ref) below the issue row.
  2. Remove the CFRelease(self.reachabilityRef) code from dealloc

Updated dealloc

- (void)dealloc {
    [self stopNotifier];

    self.reachableBlock          = nil;
    self.unreachableBlock        = nil;
    self.reachabilityBlock       = nil;
    self.reachabilitySerialQueue = nil;
}

Updated reachabilityWithAddress

+ (instancetype)reachabilityWithAddress:(void *)hostAddress {
    SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress);
    if (ref) {
        id reachability = [[self alloc] initWithReachabilityRef:ref];
        CFAutorelease(ref);
        return reachability;
    }

    return nil;
}

Updated reachabilityWithHostname

+ (instancetype)reachabilityWithHostname:(NSString*)hostname {
    SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]);
    if (ref) {
        id reachability = [[self alloc] initWithReachabilityRef:ref];
        CFAutorelease(ref);
        return reachability;
    }

    return nil;
}

latest reachability.m seems to require ARC, my app is not using it.

I just turned it on for it:

  1. go to targets\build phases\compile sources

  2. find reachability and double click on it

  3. add -fobjc-arc

memory leak is gone now

The right fix for the code is as follows in addition to CFRelease in dealloc.

look at the body of the code below. Similar body needs to go into reachabilityWithHostName code as well.

+ (instancetype)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress
{
    SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)hostAddress);

    Reachability* returnValue = NULL;

    if (reachability != NULL)
    {
        returnValue = [[self alloc] init];
        if (returnValue != NULL)
        {
            returnValue->_reachabilityRef = CFRetain(reachability);
            returnValue->_alwaysReturnLocalWiFiStatus = NO;
        }
         CFRelease(reachability);
    }
    return returnValue;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top