Why is NWPath class nil? (iOS 11, Xcode 10.1)

TLDR: the following code yields `nil` when built with Xcode 10.1 and run on iOS 11:

Class nwPathClass = [NWPath class];

IMO this should not be the case, why is this happening, can anyone explain this?


Long story:

I maintain an application that offers VPN through NEPacketTunnelProvider and use KVO on the defaultPath keypath in order to listen for network changes and update all kinds of stuff necessary to keep the tunnel functioning. Here is the code in which the above observation became apparent:

- (void) observeValueForKeyPath:(nullable NSString *) keyPath
                       ofObject:(nullable id) object
                         change:(nullable NSDictionary *) change
                        context:(nullable void *) context {
    if ([keyPath isEqualToString:@"defaultPath"]) {
        NWPath *oldPath, *newPath;
        
        id oldObject = change[NSKeyValueChangeOldKey];
        id newObject = change[NSKeyValueChangeNewKey];

        
        if ([oldObject isKindOfClass:[NWPath class]]) { // never evaluates to true
            oldPath = oldObject;
        }
        
        if ([newObject isKindOfClass:[NWPath class]]) { // never evaluates to true
            newPath = newObject;
        }
        
        if (newPath.status == NWPathStatusSatisfied && ![oldPath isEqualToPath:newObject]) {
            // update tunnel things
        }
    }
}


This code is maybe a bit too defensive, but technically this should just work. However, when this code is run on iOS 11, while it was built using Xcode 10.1, the NWPath class cannot be loaded, and therefore I am missing out on some serieous tunnel updating.


Fun fact, LLDB does not seem to agree. When I hit a breakpoint and perform the following command:

(lldb) po [NWPath class]
NWPath

I _do_ get a class...


Checking what bundle the class is from: Network.framework

(lldb) po [NSBundle bundleForClass:[NWPath class]]
NSBundle </System/Library/Frameworks/Network.framework> (loaded)


Which also kind of surprises me, should this not be NetworkExtension.framework?

Replies

Note that while nwpath is iOS 9/up, the current networking api is iOS 12/up.


Seeing that you're on Xcode 10.1, thanks, do you know which version network frameworkthat app reflects? Have you attempted to remove and re-add?

I’m not entirely sure why you’re getting

nil
here but I can shed some light on the backstory.

The Network framework, which was released as public API in iOS 12, has been private API for a long time. That private API has gone through several iterations, which muddies the water considerably. Specifically, the private Network framework historically supported C and Objective-C APIs, but the public framework only exports C and Swift. Further confusing matters is that Network Extension framework re-exports part of the Objective-C API for the benefit of Network Extension providers.

Thus, if you look at the the SDK in toto, there are three flavours of the

NWPath
API:
  • nw_path
    is the C API exported by the Network framework on iOS 12 and later (A).
  • NWPath
    is the Swift API exported by Network framework on iOS 12 and later (B). [Technically this isn’t exported by the framework but rather by the
    libswiftNetwork.dylib
    that’s copied into your app. That’s expected to change once we get Swift API stability.]
  • NWPath
    is the Objective-C API exported by the Network Extension framework on iOS 9 and later (C). Prior to iOS 12 this was a re-export of an Objective-C implementation in the private Network framework. On iOS 12 and later this is a re-export of an otherwise private Objective-C implementation contained within the public Network framework.

Note that B and C have the same name but are not equivalent. In most cases (let’s say,

UIView
) the Swift API is an import of the Objective-C API. That’s not the case here. The
NWPath
Swift API is completely different from the
NWPath
Objective-C API. For a start, the Swift API is actually a struct!

What I suspect is happening here is that Xcode 10 is finding C in the Network framework and thus telling the linker that’s where to look, but prior to iOS 12 this can’t be found there. However, I’m not 100% sure. I’d have to look into this in a lot more detail before I can give you a definitive answer.

I have two suggestions:

  • If you really want to get to the bottom of this, you should open a DTS tech support incident. That’ll give me a chance to dig deeper.

  • If you just want to get things working, you should be able to do that by looking up the class by name (using

    NSClassFromString
    ). Make sure to cache that value to avoid having to do this expensive work repeatedly.

Share and Enjoy

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

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