Skip to content

Instantly share code, notes, and snippets.

@erwinmaza
Created January 17, 2023 14:02
Show Gist options
  • Save erwinmaza/5ef34726de9809290596477dafe5f7e3 to your computer and use it in GitHub Desktop.
Save erwinmaza/5ef34726de9809290596477dafe5f7e3 to your computer and use it in GitHub Desktop.
Xcode playground code sample showing how to batch and de-dupe inflight network calls using Swift concurrency
import Foundation
func threadNumberForThread() -> String {
let array1 = Thread.current.description.components(separatedBy: ">")
if array1.count > 1 {
let array2 = array1[1].trimmingCharacters(in: CharacterSet(charactersIn: "{}")).components(separatedBy: ",")
for pair in array2 {
let array3 = pair.components(separatedBy: "=")
if array3.count > 1 {
if array3[0].contains("number") {
return array3[1].trimmingCharacters(in: CharacterSet.whitespaces)
}
}
}
}
return "(unknown)"
}
struct ResponseObject: Decodable { let name: String }
struct Test {
typealias ContinuationsMap = [String: [CheckedContinuation<(Decodable?, Error?), Never>]]
private static var pendingContinuations = ContinuationsMap()
static
func getObjectAtEndpoint<T: Decodable>(_ endpoint: String, expectingObjectOfType: T.Type) async -> (T?, Error?) {
print("enter getObjectAtEndpoint \(endpoint), thread: \(threadNumberForThread())")
// first call to this endpoint will skip this if condition
if var continuations = pendingContinuations[endpoint] {
print("get from \(endpoint) in flight, creating continuation")
// subsequent calls to same endpoint get parked here to await results from first call
let response = await withCheckedContinuation({ continuation in
continuations.append(continuation)
pendingContinuations[endpoint] = continuations
})
return (response.0 as? T, response.1)
}
// creating this key signals that this endpoint is in flight
pendingContinuations[endpoint] = []
print("calling fetchFromAPI for \(endpoint)")
let object = await fetchFromAPI(endpoint, expectingObjectOfType: T.self)
// this is always executed even if there are no pending subsequent calls
if let continuations = pendingContinuations[endpoint] {
print("resuming \(continuations.count) continuations, thread: \(threadNumberForThread())")
// each call that was "parked" above will now get the same set of results
continuations.forEach({ $0.resume(returning: object) })
// remove key from continuation map, signaling all results have been returned for this endpoint
pendingContinuations.removeValue(forKey: endpoint)
}
return object
}
private static
func fetchFromAPI<T: Decodable>(_ endpoint: String, expectingObjectOfType: T.Type) async -> (T?, Error?) {
let response = await withCheckedContinuation { continuation in
sleep(10)
print("done fetching from \(endpoint) and decoding response. No errors!")
let object = ResponseObject(name: endpoint)
let error: Error? = nil
continuation.resume(returning: (object, error))
}
return (response.0 as? T, response.1)
}
}
Task(priority: .userInitiated) {
let objects = await Test.getObjectAtEndpoint("a", expectingObjectOfType: ResponseObject.self)
}
sleep(1)
Task(priority: .background) {
let objects = await Test.getObjectAtEndpoint("b", expectingObjectOfType: ResponseObject.self)
}
sleep(1)
Task(priority: .utility) {
let objects = await Test.getObjectAtEndpoint("b", expectingObjectOfType: ResponseObject.self)
}
sleep(1)
Task(priority: .low) {
let objects = await Test.getObjectAtEndpoint("a", expectingObjectOfType: ResponseObject.self)
}
sleep(1)
Task(priority: .medium) {
let objects = await Test.getObjectAtEndpoint("c", expectingObjectOfType: ResponseObject.self)
}
sleep(1)
Task(priority: .high) {
let objects = await Test.getObjectAtEndpoint("b", expectingObjectOfType: ResponseObject.self)
}
sleep(1)
Task(priority: .utility) {
let objects = await Test.getObjectAtEndpoint("a", expectingObjectOfType: ResponseObject.self)
}
sleep(12)
Task(priority: .background) {
let objects = await Test.getObjectAtEndpoint("b", expectingObjectOfType: ResponseObject.self)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment