Last active
April 1, 2024 08:41
-
-
Save objcio-user/339c49b2555588bfae3f25fc7ab4ddc3 to your computer and use it in GitHub Desktop.
Advanced Swift Sample Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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