Once started, NWPathMonitor appears to be kept alive until cancelled, but is this documented?

NWPathMonitor appears to retain itself (or is retained by some internal infrastructure) once it has been started until cancelled. This seems like it can lead to memory leaks if the references to to the monitor are dropped. Is this behavior documented anywhere?

func nwpm_self_retain() {
    weak var weakRef: NWPathMonitor?
    autoreleasepool {
        let monitor: NWPathMonitor = NWPathMonitor()
        weakRef = monitor
        monitor.start(queue: .main)
        // monitor.cancel() // assertion fails unless this is called
    }

    assert(weakRef == nil)
}
nwpm_self_retain()
Answered by DTS Engineer in 870304022

NWPathMonitor appears to retain itself (or is retained by some internal infrastructure) once it has been started until cancelled.

Basically, yes. Start is registering a handler that's receiving events from the system, which, conceptually, ends up retaining the NWPathMonitor so that the handler has a valid target.

This seems like it can lead to memory leaks if the references to the monitor are dropped.

I haven't specifically tested it, but yes, that's certainly possible. You should cancel every NWPathMonitor that you start.

Having said that, it's also possible that your test isn't entirely valid. The "start()" process is an asynchronous action, so it's possible NWPathMonitor is temporarily holding a reference which it will drop once start later completes. If you wanted to test this properly, then you'd need to set a cancellation handler and see if that was called.

Is this behavior documented anywhere?

I'm not sure. I suspect it was documented when the API was initially released, but the network framework has gone through multiple revisions of its Swift binding, and it's possible this detail got dropped during that. However, it's not mentioned in the header doc, so it's possible it was never formally documented.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Accepted Answer

NWPathMonitor appears to retain itself (or is retained by some internal infrastructure) once it has been started until cancelled.

Basically, yes. Start is registering a handler that's receiving events from the system, which, conceptually, ends up retaining the NWPathMonitor so that the handler has a valid target.

This seems like it can lead to memory leaks if the references to the monitor are dropped.

I haven't specifically tested it, but yes, that's certainly possible. You should cancel every NWPathMonitor that you start.

Having said that, it's also possible that your test isn't entirely valid. The "start()" process is an asynchronous action, so it's possible NWPathMonitor is temporarily holding a reference which it will drop once start later completes. If you wanted to test this properly, then you'd need to set a cancellation handler and see if that was called.

Is this behavior documented anywhere?

I'm not sure. I suspect it was documented when the API was initially released, but the network framework has gone through multiple revisions of its Swift binding, and it's possible this detail got dropped during that. However, it's not mentioned in the header doc, so it's possible it was never formally documented.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thank you Kevin. It was pointed out to me that there is documentation implying that the monitor must be explicitly cancelled when it is finished being used in the generated Swift header for the type:

    /// Start the path monitor and set a queue on which path updates
    /// will be delivered.
    /// Start should only be called once on a monitor, and multiple calls to start will
    /// be ignored.
    /// Once started, the path monitor must be explicitly cancelled when it is no longer needed.
    final public func start(queue: DispatchQueue)

For whatever reason that information isn't in any of the typical documentation locations I checked (Xcode's Developer Documentation, online documentation, quick help, objc headers, etc).

Having said that, it's also possible that your test isn't entirely valid. The "start()" process is an asynchronous action, so it's possible NWPathMonitor is temporarily holding a reference which it will drop once start later completes. If you wanted to test this properly, then you'd need to set a cancellation handler and see if that was called.

Out of curiosity, is it possible to set a cancellation handler for the class-based API using an NWPathMonitor instance? i.e. is there a way to derive a nw_path_monitor_t from such an instance?

Once started, NWPathMonitor appears to be kept alive until cancelled, but is this documented?
 
 
Q