question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[REQ] [Swift] Abstract away URLSession

See original GitHub issue

Is your feature request related to a problem? Please describe.

Yes. I’m trying to implement a caching mechanism that basically overrides the URLSession.dataTask and under the hood it takes the response from the cache (if available) and at the same time loads using the URLSession itself. Currently, the only way of changing the URLSession is via override of URLSessionRequestBuilder.createURLSession (well, I can always write my own RequestBuilder, but that’d be a lot of duplicated code). So to be able to implement my approach I’d have to subclass URLSession and override the dataTask.

The implementation would look like this:

class CacheFirstURLSession: URLSession {
    override func dataTask(
        with request: URLRequest,
        completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void
    ) -> URLSessionDataTask {
        if let cachedResponse = configuration.urlCache?.cachedResponse(for: request) {
            completionHandler(cachedResponse.data, cachedResponse.response, nil)
        }

        return super.dataTask(with: request, completionHandler: completionHandler)
    }
}

This is not currently doable (as far as I know, URLSessions are not subclassable). I tried a couple of times to make a subclass, but it does not work - calling dataTask crashes the app. Probably because there’s no way to inject configuration into a subclass.

Describe the solution you’d like

So I don’t necessarily need a subclass of the URLSession, I can go with a wrapper like :

class URLSessionWrapper {
    private let wrapped: URLSession
    
    init(with wrapped: URLSession) {
        self.wrapped = wrapped
    }
}

extension URLSessionWrapper: DataTaskMaking {
    func dataTask(
        with request: URLRequest,
        completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void
    ) -> URLSessionDataTask {
        if let cachedResponse = wrapped.configuration.urlCache?.cachedResponse(for: request) {
            completionHandler(cachedResponse.data, cachedResponse.response, nil)
        }

        return wrapped.dataTask(with: request, completionHandler: completionHandler)
    }
}

The only problem that I have now is that the URLSessionRequestBuilder.createURLSession returns the exact URLSession. If we’d be able to exchange it into a protocol then my problem is solved.

Here’s an initial idea of what we’d have to do in the URLSessionImplementation.mustache:

Somewhere at the top (or bottom):

public protocol DataTaskMaking {
    func dataTask(
        with request: URLRequest,
        completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void
    ) -> URLSessionDataTask
}

extension URLSession: DataTaskMaking {}

And then change the override:

{...} func createURLSession() -> DataTaskMaking {
    return defaultURLSession
}

Describe alternatives you’ve considered

So I tried solving the problem with subclassing and extensions which both don’t seem to be feasible in our current architecture. Another possible option is to create my own own RequestBuilder, but that’d be a lot of duplicated code.

Additional context

Nope

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:1
  • Comments:9 (8 by maintainers)

github_iconTop GitHub Comments

2reactions
leszek-scommented, Apr 8, 2022

Hello @4brunu, I saw that example in BearerDecodableRequestBuilder.swift and yes that works for implementing token refreshing but still subclassing URLSession would be better but unfortunately it is not possible. As @Czajnikowski wrote subclassing func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask crashes the app with SIGABORT error. I think it was possible to subclass URLSession some time ago but currently it is not working I just checked with this code which always crashes on call to super.dataTask:

class MyURLSession: URLSession {
    static let defaultUrlSession = MyURLSession()
    override func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
        return super.dataTask(with: request, completionHandler: completionHandler)
    }
}

and

    override func createURLSession() -> URLSession {
        return MyURLSession.defaultUrlSession
    }

It also shows a warning that 'init()' was deprecated in iOS 13.0 next to MyURLSession() and if you open URLSession definition you can also see this in Apple’s code:

    public /*not inherited*/ init(configuration: URLSessionConfiguration)

    public /*not inherited*/ init(configuration: URLSessionConfiguration, delegate: URLSessionDelegate?, delegateQueue queue: OperationQueue?)

So it is currently not possible to properly initialize subclassed URLSession and that explains why it crashes if you try to do that. Therefore without abstracting URLSession replacing it by overriding createURLSession is currently not very useful. Also this stack overflow topic confirms problems with subclassing URLSession: https://stackoverflow.com/questions/48158484/subclassing-factory-methods-of-urlsession-in-swift If instead of URLSession we could provide anything that confirms to a protocol as proposed by @Czajnikowski then that would solve this problem.

1reaction
4brunucommented, Apr 11, 2022

Could you please open a PR?

Read more comments on GitHub >

github_iconTop Results From Across the Web

How return data from an HTTP request in Swift/Objective C
In the edit to your question, you say: I need to RETURN the data which I receive from the response. Both answers (and...
Read more >
Destroy some basic HttpManager in Swift | by Jim Lai | Medium
Since he chose to couple mocking with URLSession, he had to abstract away URLSession to protocol. One mistake would often lead to another ......
Read more >
HTTP in Swift, Part 17: Brain Dump | Dave DeLong
If it is, then the new request is set aside, and when the original request gets a response, that response is duplicated to...
Read more >
How to mock Alamofire and URLSession requests in Swift
Mocking Alamofire data requests · We've registered the MockingURLProtocol with our Alamofire manager · We execute the request to fetch the user ...
Read more >
URLSession | Apple Developer Documentation
Asynchronicity and URL Sessions · If you're using Swift, you can use the methods marked with the async keyword to perform common tasks....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found