Section 8/161 menit
8. Custom AsyncSequence
8. Custom AsyncSequence
Menulis AsyncSequence custom yang benar membutuhkan pemahaman tentang cooperative cancellation dan memory management.
Protocol Requirements
swift
// AsyncSequence membutuhkan: Element, makeAsyncIterator(), dan AsyncIterator
// AsyncIterator membutuhkan: next() async throws -> Element?
// Pattern paling umum: struct sequence + struct iterator
struct CountdownSequence: AsyncSequence {
typealias Element = Int
let from: Int
let delaySeconds: Double
func makeAsyncIterator() -> CountdownIterator {
CountdownIterator(current: from, delay: delaySeconds)
}
}
struct CountdownIterator: AsyncIteratorProtocol {
var current: Int
let delay: Double
// next() dipanggil setiap `for await` iteration
mutating func next() async throws -> Int? {
guard current > 0 else { return nil } // nil = sequence selesai
// Check cancellation sebelum operasi async
try Task.checkCancellation()
// Operasi async
try await Task.sleep(for: .seconds(delay))
// Check cancellation lagi setelah sleep
try Task.checkCancellation()
defer { current -= 1 }
return current
}
}
// Penggunaan
for try await count in CountdownSequence(from: 5, delaySeconds: 1.0) {
print(count) // 5, 4, 3, 2, 1
}
AsyncSequence dengan State dan Cancellation
swift
// AsyncSequence untuk monitoring file changes
struct FileChangeSequence: AsyncSequence {
typealias Element = FileChange
let url: URL
func makeAsyncIterator() -> FileChangeIterator {
FileChangeIterator(url: url)
}
}
struct FileChangeIterator: AsyncIteratorProtocol {
let url: URL
private var stream: AsyncStream<FileChange>.Iterator?
init(url: URL) {
self.url = url
// Setup stream di init
let (stream, continuation) = AsyncStream.makeStream(of: FileChange.self)
self.stream = stream.makeAsyncIterator()
// Start file monitoring — continuation.finish() saat deinit
FileMonitor.start(url: url, continuation: continuation)
}
mutating func next() async -> FileChange? {
// Delegate ke underlying AsyncStream
await stream?.next()
}
}
struct FileChange { let path: String; let type: ChangeType }
enum ChangeType { case created, modified, deleted }
class FileMonitor {
static func start(url: URL, continuation: AsyncStream<FileChange>.Continuation) {
// Platform-specific file monitoring
// Di macOS: DispatchSource.makeFileSystemObjectSource
}
}
AsyncSequence dengan Generics
swift
// Reusable paginated sequence
struct PaginatedSequence<Item: Decodable & Sendable>: AsyncSequence {
typealias Element = Item
let fetchPage: @Sendable (Int) async throws -> PageResult<Item>
struct PageResult<T> {
let items: [T]
let hasMore: Bool
}
func makeAsyncIterator() -> PaginatedIterator {
PaginatedIterator(fetchPage: fetchPage)
}
struct PaginatedIterator: AsyncIteratorProtocol {
private var currentPage = 0
private var buffer: [Item] = []
private var hasMore = true
private let fetchPage: @Sendable (Int) async throws -> PageResult<Item>
init(fetchPage: @Sendable (Int) async throws -> PageResult<Item>) {
self.fetchPage = fetchPage
}
mutating func next() async throws -> Item? {
// Kembalikan dari buffer jika ada
if !buffer.isEmpty {
return buffer.removeFirst()
}
// Buffer kosong dan tidak ada halaman lagi
guard hasMore else { return nil }
try Task.checkCancellation()
// Fetch halaman berikutnya
let result = try await fetchPage(currentPage)
currentPage += 1
hasMore = result.hasMore
buffer = Array(result.items.dropFirst()) // simpan sisa di buffer
return result.items.first
}
}
}
// Penggunaan: iterasi semua user tanpa load semua ke memori
let allUsers = PaginatedSequence { page in
let response = try await API.fetchUsers(page: page, pageSize: 50)
return .init(items: response.users, hasMore: response.hasMore)
}
for try await user in allUsers {
process(user)
}
struct APIUser: Decodable, Sendable { let id: String }
enum API { static func fetchUsers(page: Int, pageSize: Int) async throws -> (users: [APIUser], hasMore: Bool) { ([], false) } }
func process(_ user: APIUser) {}