-
-
Notifications
You must be signed in to change notification settings - Fork 284
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for the new URLSession async/await APIs #113
Comments
I'm pretty sure it used to work in the previous versions, but yes, I just tested it, and the new async/await APIs on URLSession such as |
I thought that might be the case so I tried to work around it with the following but it runs into the same issue, which makes me think the issue is more related to the new concurrency system then URLSession's concurrency methods themselves. func fetchTodos() async throws -> Data {
let url = URL(string: "https://jsonplaceholder.typicode.com/todos/1")!
// let (data, _) = try await session.data(from: url)
// return data
return try await withCheckedThrowingContinuation { continuation in
let task = session.dataTask(with: URLRequest(url: url)) { data, _, error in
if let error {
continuation.resume(throwing: error)
return
}
continuation.resume(returning: data ?? Data())
}
task.resume()
}
} |
+1 |
@vdka , the completion-based methods also don't call the delegate. There is probably a way to swizzle some of the |
I believe we have to use |
The same "pending" issue appears if you use Combine |
I'd love to make a PR to fix this, however I am not likely to get the time. If anybody is interested in looking into this, I know ProxymanApp/Atlantis appears to be unaffected. It appears to Swizzle internal Method's and so AppReview would likely not approve, however, could be offered as an option similar to the existing Experimental integration approach |
I'm assuming the |
For the completion handler case, is it possible to tell the pulse logger after the completion block is called that the request is complete? |
Hi @kean I've exactly the same problem with "new" Swift concurrency URLSession as requests are always shown as "Pending..." when they are done like this:
As this is not possible/acceptable for our iOS team to modify all requests in another way than modern Swift concurrency Thanks for your answer and again: Big up for all your work 👏 Have a nice day Jeremie |
The experimental URL protocol stuff wasn't working for me so I came up with a different workaround for our API client implementation. I've only tested this with data tasks, but it looks like it's the final
Every time my API client starts a new request, it creates a new instance of my proxy delegate, passing in a reference to the session and the actual delegate. It then passes this to the async function e.g. do {
let proxyDelegate = ProxyDelegate(session: session, actualDelegate: actualDelegate)
let (data, response) = try await session.data(for: request, delegate: proxyDelegate)
proxyDelegate.notifyCompletion(error: nil)
return (data, response)
} catch {
proxyDelegate.notifyCompletion(error: error)
throw error
} |
Just adding my workaround here: if you're using It relies on creating a fake extension URLSession {
func trackedDataRequest(for urlRequest: URLRequest, with networkLogger: NetworkLogger) -> AnyPublisher<DataTaskPublisher.Output, DataTaskPublisher.Failure> {
let dataTask = dataTask(with: urlRequest)
return dataTaskPublisher(for: urlRequest)
.handleEvents(
receiveSubscription: { _ in
networkLogger.logTaskCreated(dataTask)
},
receiveOutput: { output in
networkLogger.logDataTask(dataTask, didReceive: output.data)
},
receiveCompletion: { completion in
switch completion {
case .finished:
networkLogger.logTask(dataTask, didCompleteWithError: nil)
case let .failure(error):
networkLogger.logTask(dataTask, didCompleteWithError: error)
}
}
)
.eraseToAnyPublisher()
}
} |
For me, solution was to transform codebase to use public protocol URLSessionProtocol {
func dataTask(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
} Then, Pulse can be easily integrated: class URLSessionPulse: URLSessionProtocol {
let session: URLSession
let networkLogger: NetworkLogger
init(session: URLSession) {
self.session = session
self.networkLogger = NetworkLogger()
}
func dataTask(with request: URLRequest, completionHandler: @escaping @Sendable (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
var task: URLSessionDataTask?
let onReceive: (Data?, URLResponse?, Error?) -> Void = { (data, response, error) in
if let task {
if let data {
self.networkLogger.logDataTask(task, didReceive: data)
}
self.networkLogger.logTask(task, didCompleteWithError: error)
}
}
task = session.dataTask(with: request) {data, response, error in
onReceive(data, response, error)
completionHandler(data, response, error)
}
if let task {
self.networkLogger.logTaskCreated(task)
}
return task!
}
} |
According to docs, if you're specifying completion handler (use async version that uses it under the hood or use dataTaskPublished that uses completion handler as well) it will ignore data delegate note section of docs. I wonder how it worked before? |
More generic solution that uses extension URLSession {
/// Allows to track `URLSessionDataDelegate` using closure based call.
/// By default if you use async interface or `completionHandler` based interface,
/// URLSession won't notify `URLSessionDataDelegate`.
public func dataTask(for request: URLRequest) async throws -> (Data, URLResponse) {
var dataTask: URLSessionDataTask?
let onSuccess: (Data, URLResponse) -> Void = { (data, response) in
guard let dataTask, let dataDelegate = self.delegate as? URLSessionDataDelegate else {
return
}
dataDelegate.urlSession?(self, dataTask: dataTask, didReceive: data)
dataDelegate.urlSession?(self, task: dataTask, didCompleteWithError: nil)
}
let onError: (Error) -> Void = { error in
guard let dataTask, let dataDelegate = self.delegate as? URLSessionDataDelegate else {
return
}
dataDelegate.urlSession?(self, task: dataTask, didCompleteWithError: error)
}
let onCancel = {
dataTask?.cancel()
}
return try await withTaskCancellationHandler(operation: {
try await withCheckedThrowingContinuation { continuation in
dataTask = self.dataTask(with: request) { data, response, error in
guard let data = data, let response = response else {
let error = error ?? URLError(.badServerResponse)
onError(error)
return continuation.resume(throwing: error)
}
onSuccess(data, response)
continuation.resume(returning: (data, response))
}
dataTask?.resume()
}
}, onCancel: {
onCancel()
})
}
} |
@salmin-skelia where delegate (URLSessionDataDelegate) is used? can you show all code please? |
What do you mean by |
Simple integration using
URLSessionProxyDelegate
on iOS 16.0 is creating the request in logs, but they are stuck "Pending". I did a little bit of debugging and it seemsurlSession(_:task:didCompleteWithError:)
isn't being called by the system.Reproduction
The text was updated successfully, but these errors were encountered: