Replacing SCNetworkReachability with NWPathMonitor

At WWDC this year, I was in the Network.framework session where the presenter had mentioned to switch from using SCNetworkReachability and use the capabilities provided by the new Network.framework. I'm in the process of doing so, but I'm unsure as to how to proceed with identifying the network interface that the update is for. In the SCNetworkReachability world, you call SCNetworkReachabilityGetFlags and check the value by ANDing with the interface you want. For the nw_path_t, do you call the nw_path_enumerate_interfaces function and try to query each nw_interface_t or do you make repeated calls to nw_path_uses_interface_type to determine the interface?


Sample:

- (void)setupNetworkMonitoring
{
    dispatch_queue_attr_t attrs = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, DISPATCH_QUEUE_PRIORITY_DEFAULT);
    self.monitorQueue = dispatch_queue_create("com.example.network-monitor", attrs);
    
    // self.monitor = nw_path_monitor_create_with_type(nw_interface_type_wifi);
    self.monitor = nw_path_monitor_create();
    nw_path_monitor_set_queue(self.monitor, self.monitorQueue);
    nw_path_monitor_set_update_handler(self.monitor, ^(nw_path_t _Nonnull path) {
        nw_path_status_t status = nw_path_get_status(path);
        // Determine the active interface, but how?
        switch (status) {
            case nw_path_status_invalid: {
                // Network path is invalid
                break;
            }
            case nw_path_status_satisfied: {
                // Network is usable
                break;
            }
            case nw_path_status_satisfiable: {
                // Network may be usable
                break;
            }
            case nw_path_status_unsatisfied: {
                // Network is not usable
                break;
            }
        }
    });
    
    nw_path_monitor_start(self.monitor);
}

Accepted Reply

Thanks for the further explanation.

The remaining task is to determine what interface the path is using, so do I A)

nw_path_enumerate_interfaces
to get the interfaces or B) make multiple calls to
nw_path_uses_interface_type
to determine what is in use?

Either will work but my preference is for B because it’s seems a lot more direct.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

It’s hard to answer this without knowing more about your high-level goal. What are you currently using reachability for? Basic connectivity testing? Determining whether the connection is expensive (like running over WWAN)?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi Quinn. Some of our apps have the functional requirement of disabling functionality when the user does not have a network connection and adjusts the UI accordingly. The network interface in the reachability update allows us to show the correct image alongside an appropriate string. Note: using NSURLSessionConfiguration's waitsForConnectivity doesn't quite fit the bill.

Just as an aside, I generally disagree with the notion of disabling functionality when there’s no network available. The problem is that reachability (both

SCNetworkReachability
and
NWPathMonitor
) is not perfect; it can result in both false positives (saying that something is reachable when it’s not) and false negatives (saying that something is unreachable when it is). It also suffers from TOCTTOU issues.

Consider this scenario:

  1. User is on a train, writing an email in Mail.

  2. Just as their done, the train goes into a tunnel.

  3. Mail detects that and disables its Send button.

  4. User swears loudly.

  5. User has to wait for the train to leave the tunnel before they can tap Send.

A better approach, and the one actually used by Mail, is that to keep the Send button enabled and, when the user sends the message, it starts a network request for it. That request will run as soon as the train comes out of the tunnel.

So my recommendation is that you never disable UI based on the network conditions, but rather always let the user try the operation and then display sensible status information when it’s not working.

Having said that, this does not obviate your question because it is still useful to provide feedback to the user as to whether something is likely to succeed.

So, coming back to your original question, I’m confused by your actual concern. If you want to monitor the availability of the default route, you create a path monitor using

nw_path_monitor_create
. The update handler is then called with a new path every time the default route changes, and you can use
nw_path_get_status
to get the status of that path. What else do you need?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I'd like to know what interface the path is using when the status changes, e.g. Wi-Fi to Cellular, Cellular to Wi-Fi, Wi-Fi to nothing. Since I'm trying to replace SCNetworkReachability, here is what we get today when the callback is called:

static void ExampleReachabiltiyCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *_Nullable info)
{
    BOOL reachableViaWiFi = (flags & kSCNetworkReachabilityFlagsReachable) && !(flags & kSCNetworkReachabilityFlagsIsWWAN);
    BOOL reachableViaCellular = (flags & kSCNetworkReachabilityFlagsReachable) && (flags & kSCNetworkReachabilityFlagsIsWWAN);
    BOOL connectionRequired = (flags & kSCNetworkReachabilityFlagsConnectionRequired);
    BOOL connectionIsDynamic = (flags & kSCNetworkReachabilityFlagsConnectionRequired) && (flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand));
    BOOL userActionRequired = (flags & kSCNetworkReachabilityFlagsConnectionRequired) && (flags & kSCNetworkReachabilityFlagsInterventionRequired);
    
    NSDictionary *userInfo = @{
                                @"reachabileViaWIFI" : @(reachableViaWiFi),
                                @"reachableViaCellular" : @(reachableViaCellular),
                                @"connectionRequired" : @(connectionRequired),
                                @"connectionIsDynamic" : @(connectionIsDynamic),
                                @"userActionRequired" : @(userActionRequired),
                                @"hostname" : [(__bridge ExampleReachability *)info valueForKey:@"hostname"]
                             };
    NSNotification *notification = [NSNotification notificationWithName:@"com.example.reachability.status-change" object:nil userInfo:userInfo];
    
    if (![NSThread isMainThread]) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [NSNotificationCenter.defaultCenter postNotification:notification];
        });
    }
    else {
        [NSNotificationCenter.defaultCenter postNotification:notification];
    }
}


Now, most of the information contained in the SCNetworkReachabilityFlags aren't that useful to us, but for the sake of completeness we compute them. However, the main focus is the reachableViaWiFi and the reachableViaCellular calculations. To go back to the NWPathMonitor example in my first post, the update block does get a nw_path_t and I do call get status on it. The remaining task is to determine what interface the path is using, so do I A) nw_path_enumerate_interfaces to get the interfaces or B) make multiple calls to nw_path_uses_interface_type to determine what is in use?

Thanks for the further explanation.

The remaining task is to determine what interface the path is using, so do I A)

nw_path_enumerate_interfaces
to get the interfaces or B) make multiple calls to
nw_path_uses_interface_type
to determine what is in use?

Either will work but my preference is for B because it’s seems a lot more direct.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Awesome, thanks Quinn.