Skip to content

Instantly share code, notes, and snippets.

@objcio-user
Last active April 1, 2024 08:41
Show Gist options
  • Save objcio-user/339c49b2555588bfae3f25fc7ab4ddc3 to your computer and use it in GitHub Desktop.
Save objcio-user/339c49b2555588bfae3f25fc7ab4ddc3 to your computer and use it in GitHub Desktop.
Advanced Swift Sample Code
import Foundation
struct Episode: Identifiable, Codable {
var id: String
var poster_url: URL
var collection: String
// ...
static let url = URL(string: "https://talk.objc.io/episodes.json")!
}
struct Collection: Identifiable, Codable {
var id: String
var title: String
static let url = URL(string: "https://talk.objc.io/collections.json")!
}
struct UnknownError: Error { }
// Load the first poster image (for which you need to load the ST data first), with cancellation support
/// A thread-safe cancellation token.
final class Cancellable {
private var _isCancelled: Bool
private var onCancel: (() -> Void)?
private let queue = DispatchQueue(label: "Cancellable")
init(onCancel: @escaping () -> Void) {
self._isCancelled = false
self.onCancel = onCancel
}
var isCancelled: Bool {
queue.sync { _isCancelled }
}
func cancel() {
queue.async { [self] in
_isCancelled = true
onCancel?()
onCancel = nil
}
}
}
func loadFirstPosterContCancellable(_ completion: @escaping (Result<Data, Error>) -> ()) -> Cancellable {
let session = URLSession.shared
var outerTask: URLSessionDataTask? = nil
var innerTask: URLSessionDataTask? = nil
let cancellable = Cancellable {
outerTask?.cancel()
innerTask?.cancel()
}
outerTask = session.dataTask(with: Episode.url) { data, _, err in
do {
guard let d = data else {
throw (err ?? UnknownError())
}
let episodes = try JSONDecoder().decode([Episode].self, from: d)
// Check for cancellation again after JSON decoding.
// This is necessary because innerTask == nil at this point.
guard !cancellable.isCancelled else {
throw CancellationError()
}
innerTask = session.dataTask(with: episodes[0].poster_url, completionHandler: { data, _, err in
completion(Result {
guard let d = data else {
throw (err ?? UnknownError())
}
return d
})
})
innerTask!.resume()
} catch {
completion(.failure(error))
}
}
outerTask!.resume()
return cancellable
}
// Sample code for Advanced Swift
import Foundation
struct Episode: Identifiable, Codable {
var id: String
var poster_url: URL
var collection: String
// ...
static let url = URL(string: "https://talk.objc.io/episodes.json")!
}
struct Collection: Identifiable, Codable {
var id: String
var title: String
static let url = URL(string: "https://talk.objc.io/collections.json")!
}
struct UnknownError: Error { }
func loadPosterImagesCont(for episodes: [Episode], _ completion: @escaping (Result<[Episode.ID: Data], Error>) -> ()) {
DispatchQueue.global().async {
// Access to shared state from multiple threads must be synchronized.
var childResults: [Result<(Episode.ID, Data), Error>] = []
let resultQueue = DispatchQueue(label: "protect state")
// DispatchGroup allows the "parent task" to wait for the "child tasks".
let group = DispatchGroup()
let session = URLSession.shared
for episode in episodes {
group.enter()
let task = session.dataTask(with: episode.poster_url) { imageData, _, error in
resultQueue.sync {
defer { group.leave() }
if let imageData = imageData {
childResults.append(.success((episode.id, imageData)))
} else {
childResults.append(.failure(error ?? UnknownError()))
}
}
}
task.resume()
}
// Wait for "child tasks" to complete.
group.notify(queue: resultQueue) {
completion(Result {
try childResults.reduce(into: [:]) { (dict, pair) in
let (episodeID, imageData) = try pair.get()
dict[episodeID] = imageData
}
})
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment