Apple's documentation for the class
URLSession for Swift coders) contains a warning marked "Important":
The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you don’t invalidate the session, your app leaks memory until the app terminates.
This warning doesn't tell the whole story, though. It's incomplete. What it doesn't tell you is that if you don't invalidate the session (via finishTasksAndInvalidate or invalidateAndCancel), then the internet connection created by the session remains open until the app terminates, even after the delegate method URLSession:task:didCompleteWithError: has been called, and even after the app's code no longer has a strong reference to the session. It's more than just a potential memory leak.
I discovered this connection leak the same way I discover a lot of things about how macOS works: by accident, with Little Snitch. I confirmed it with a sample Xcode project and packet traces. (Note that in my sample app the
NSURLSession delegate is also the
NSApplication delegate, which is expected to remain instantiated for the lifetime of an app, so there's no worry about a memory leak.)
My sample app runs two consecutive ephemeral sessions with
https://www.reddit.com, a URL chosen mostly arbitrarily. I did want something other than
apple.com in order to stand out in packet traces that can also include system processes phoning home to Cupertino. I ran two sessions to see whether
NSURLSession would reuse the connection, but it did not; the two ports were different.
The packet traces show that when my app calls
invalidateAndCancel after the
URLSession:task:didCompleteWithError: delegate method, my Mac immediately sends a TCP FIN packet to Reddit, closing the connection. On the other hand, if
invalidateAndCancel isn't called, then… nothing happens. There's no further traffic on the connection, but it remains open. Indeed, both connections from both sessions remain open. This can be verified for example with the
netstat command-line tool.
My Mac finally sends TCP FIN packets on both connections, simultaneously, after
applicationWillTerminate: is called, so it becomes obvious that the connections were leaked for the lifetime of the app process.
NSURLSession API seems peculiar, because you would expect
URLSession:task:didCompleteWithError: to be, you know, the end. Shouldn't you be able to freely (pun intended) dispose of the connection at that point? The reality, however, is that you need to invalidate every used session. So now I know, and now you know.
You can see in Xcode's "View Memory Graph Hierarchy" that the system framework via
URLProtocol still has references to the two
NSURLSession objects even though the app no longer has references to them. The docs do not mention this second memory leak, only the first memory leak of the
NSURLSession keeping a reference to its delegate (in this case