I finally have time and the toolchains seem to be stable and featureful enough for me to start experimenting with a new core Alamofire design using the Swift concurrency features. I have a few questions and would like any general design feedback. Here's my simple program:
actor Session {
private(set) lazy var session = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil)
private(set) lazy var sessionDelegate = SessionDelegate(self)
private(set) var requests: [URLSessionTask: DataRequest] = [:]
func request() async throws -> DataRequest {
let request = DataRequest()
let task = await request.task(using: session)
requests[task] = request
task.resume()
return request
}
func request(for task: URLSessionTask) -> DataRequest? {
requests[task]
}
}
actor SessionDelegate: NSObject {
private weak var owner: Session?
init(_ owner: Session) {
self.owner = owner
}
}
extension SessionDelegate: URLSessionTaskDelegate {
@asyncHandler
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
await owner?.request(for: task)?.didComplete(error)
}
}
extension SessionDelegate: URLSessionDataDelegate {
@asyncHandler
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
await owner?.request(for: dataTask)?.didReceive(data)
}
}
actor DataRequest {
private(set) var data = Data()
private(set) var error: Error?
private var completion: ((Data, Error?) -> Void)?
func task(using session: URLSession) -> URLSessionTask {
session.dataTask(with: URLRequest(url: URL(string: "https://httpbin.org/get")!))
}
func didComplete(_ error: Error?) {
self.error = error
print("didComplete")
completion?(data, self.error)
}
func didReceive(_ data: Data) {
self.data.append(data)
}
func awaitCompletion() async throws -> Data {
try await withUnsafeThrowingContinuation { continuation in
self.completion = { data, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: data)
}
}
}
}
}
runAsyncAndBlock {
let session = Session()
do {
let data = try await session.request().awaitCompletion()
print(String(decoding: data, as: UTF8.self))
} catch {
print(error)
}
}
-
runAsyncAndBlockis deprecated. Is there a replacement I can use to provide an async context in a simple executable? - I'm currently unable to directly set the
SessionDelegate'sownerfrom aSessioninitializer due to safety errors. What's the intended pattern for this sort of connection? I can seenonisolated(unsafe)eventually working, but I'd really like to know that the suggested safe solution is supposed to be. - Relatedly, it seems like sharing the execution context between the
SessionandSessionDelegateactors would help, but that functionality doesn't seem to exist in the snapshot. Might that be an appropriate approach once we can customize theExecutor's? - I thought the
URLSessiondelegate methods were supposed to marked@asyncHandlerwhen they're imported from Obj-C. Has that not been done yet? Will it? - Relatedly, I assume Apple's frameworks will be audited for
ConcurrentValueconformance? This seems like a big job, is there any way the community can help? - Relatedly, will the current "Cannot pass argument of non-concurrent-value..." warning be an error once that audit is complete, or is this meant to only be a warning? Adding
UnsafeConcurrentValueto say,URLSession, doesn't actually fix it right now. Bug or limitation? - I'd like my
DataRequesttype to offer anasynccompletion API, but there doesn't seem to be a great way to trigger such an event. My current approach of setting a closure which resumes anUnsafeContinuationseems to work, but surely that can't be the recommended approach. Is there a feature planned to make creating APIs similar toTask.Handleeasier? - This program completes the network request but then crashes. Reported as SR-14283.
- Any other suggestions?