Ad

Our DNA is written in Swift
Jump

Simple Reachability + Blocks

The Reachability sample is arguable the one piece of Apple sample code that is seen in most wide use. But it is just that: a sample.

It was lacking in some regards and so a plethora of variants popped up, all with varying version numbers. But all these version numbers do is to mask the fact that these are not “official” versions of sanctioned Apple code.

Yet another problem is that people needed to start prefixing their versions of Reachability as to avoid conflicts with other people’s Reachability contained in other components.

For the latest version of AutoIngest for Mac I wanted to understand the core of what is necessary to do monitoring of the Internet connection. And then develop my own reachability solution that does not have this baggage. Something that I could include in all my projects via DTFoundation without having to fear linker conflicts or code duplication.

As a starting point, to understand the basic concepts, I turned to Erica Sadun’s UIDevice (Reachability) category which is bold in its own right, since it is one of very few pieces of code that don’t trace their lineage back to Apple’s Reachability sample.

SystemConfiguration Basics

The main reason for Reachability’s popularity is that there is no Objective-C API for achieving connection monitoring. You have to go down to the Core Foundation level, where you find all the necessary C-functions for this purpose. All Objective-C Reachability derivatives are basically wrappers around SCNetworkReachability.

What follows are the basic ingredients to use SCNetworkReachability, which I shall abbreviate as SCNR henceforth.

First you need to define a call-back function that will be called by SCNR whenever there is a change in network status. This function gets are reference to the SCNR instance, SCNetworkConnectionFlags informing about the status and a void * info pointer which you can use to keep track of some state or context.

static void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkConnectionFlags flags, void* info)
{
   // do something with the flags
}

The second step is to define the addresses that this SCNR will be watching for.

BOOL ignoresAdHocWiFi = NO;
 
struct sockaddr_in ipAddress;
bzero(&ipAddress, sizeof(ipAddress));
ipAddress.sin_len = sizeof(ipAddress);
ipAddress.sin_family = AF_INET;
ipAddress.sin_addr.s_addr = htonl(ignoresAdHocWiFi ? INADDR_ANY : IN_LINKLOCALNETNUM);

Erica has got this BOOL value ignoresAdHocWiFi which you can set to YES if you are not interested in those kinds of connections.

The third step is to create an SCNR instance, set the callback function and schedule it on a run loop.

// create a reachability
_reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (struct sockaddr *)&ipAddress);
 
// create a context, this gets passed to function as info
SCNetworkReachabilityContext context = {0, NULL, NULL, NULL, NULL};
 
// set callback function        
if (SCNetworkReachabilitySetCallback(_reachability, ReachabilityCallback, &context))
{
   if (!SCNetworkReachabilityScheduleWithRunLoop(_reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes))
   {
      NSLog(@"Error: Could not schedule reachability");
 
      // something went wrong, unset callback
      SCNetworkReachabilitySetCallback(_reachability, NULL, NULL);
 
      return nil;
   }
}

Note that we are choosing to schedule the SCNR on the main run loop and the common modes. The main run loop is the only one we can rely on to be present, the common modes means that it does not get paused if we scroll a UIScrollView.

With the SCNR successfully scheduled we could either wait for the next change in flags to trigger our call back function. Or we can always get the current state with SCNetworkReachabilityGetFlags.

// get the current flags if possible
if (SCNetworkReachabilityGetFlags(_reachability, &_connectionFlags))
{
   // we got flags, do something with it
}

To clean up, we unset the call back function and unschedule the SCNR.

SCNetworkReachabilitySetCallback(_reachability, NULL, NULL);
 
if (SCNetworkReachabilityUnscheduleFromRunLoop(_reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes))
{
   NSLog(@"Unscheduled reachability");
}
else
{
   NSLog(@"Error: Could not unschedule reachability");
}
 
// this is CF, so we need to nil our IVAR
_reachability = nil;

These are the basic ingredients for our own Reachability.

Block-based Reachability

With these basic parts down, I created DTReachability for inclusion in my DTFoundation toolkit, with this API:

// observer block
typedef void(^DTReachabilityObserverBlock)(SCNetworkConnectionFlags connectionFlags);
 
@interface DTReachability : NSObject
 
/**
 Adds a block to observe network reachability. Every time the reachability flags change this block is invoked. Also once right after adding the observer with the current state.
 @param observer An observation block
 @returns An opaque reference to the observer which you can use to remove it
 */
+ (id)addReachabilityObserverWithBlock:(DTReachabilityObserverBlock)observer;
 
 
/**
 Removes a reachability observer.
 @param observer The opaque reference to a reachability observer
 */
+ (void)removeReachabilityObserver:(id)observer;
 
@end

The observer is a block that receives the current connection flags. You can add reachability observers for any object in your code via the class method to add them.

Internally DTReachability keeps these observers in a mutable set and uses a single SCNR to service these. Similar to the block-based API for NSNotificationCenter you receive back an opaque reference from the registration which you can use later on to unregister the observer.

Upon registration of an observer the class also tries to immediately get the current network flags and if successful calls the observer block right away with these.

Here’s is how this is used in AutoIngest for Mac:

// make a weak ref
__weak DTITCReportManager *weakself = self;
 
_reachabilityObserver = [DTReachability addReachabilityObserverWithBlock:^(SCNetworkConnectionFlags connectionFlags) {
 
   // assign to strong first
   DTITCReportManager *manager = weakself;
 
   // store current network flags
   manager->_connectionFlags = connectionFlags;
 
   BOOL hasConnection = [manager _hasInternetConnection];
 
   if (hasConnection)
   {
      NSLog(@"Has Internet Connection");
   }
   else
   {
      NSLog(@"NO Internet Connection");
   }
 
   if (manager->_waitingForConnectionToSync && hasConnection)
   {
      NSLog(@"Internet became available and waiting for that to sync");
      [manager startSync];
   }
}];

This uses a helper method _hasInternetConnect which inspects the current network flags to determine if internet is connected:

- (BOOL)_hasInternetConnection
{
    BOOL isReachable = ((_connectionFlags & kSCNetworkFlagsReachable) != 0);
    BOOL needsConnection = ((_connectionFlags & kSCNetworkFlagsConnectionRequired) != 0);
    return (isReachable && !needsConnection);
}

Note that we have to convert the weak reference to a strong one first in the block. The reason for this is that weak references might turn to nil during execution of the block. And dereferences a public IVAR from a NULL causes a runtime exception. Also Xcode will alert us to that fact.

As an alternative we could have created a private setter. There it would not have mattered if the references became nil since you can always message nil with anything and it will be a no-op.

Conclusion

I like this block-based approach much more as opposed to listening for notifications or having a delegate callback on a central Reachability instance.

In most uses-cases I have seen so far you are probably just calling a method on the watching view controller and there it helps that the observer block can capture some state. Because of this we don’t need to use the C-level state passing.

At the time of this writing the code for DTReachability is now present on the develop branch of DTFoundation, it will be merged into master for the next release. Since it has a dependency on the SystemConfiguration.framework I put it into its own static lib and Cocoapods sub spec to use individually.

I am using this in AutoIngest for Mac now, but it should work without modification on iOS just the same. Your feedback is welcome.


Categories: Recipes

15 Comments »