Automatic lifetime management for async sequences, preventing retain cycles and ensuring proper resource cleanup in Swift's async/await world.
AsyncLifetime provides a suite of functions that automatically bind async sequence processing to object lifetimes. When the target object is deallocated, any ongoing async operations are automatically cancelled, preventing retain cycles and unnecessary computation.
The core issue stems from Swift's Task
initializer which uses @_implicitSelfCapture
. Consider this typical pattern in view model initialization:
@MainActor
@Observable
class Model {
var items: [String] = []
init(dataStream: AsyncStream<String>) {
Task {
for await item in dataStream {
items.append(item) // self is implicitly strongly captured by Task
}
}
}
}
The Problem: The Task
holds a strong reference to self
(the ViewModel), preventing it from being deallocated even when the UI is dismissed.
AsyncLifetime uses weak references to break retain cycles, automatically cancelling operations when the target object is deallocated:
@MainActor
@Observable
class Model {
var items: [String] = []
private var cancellables = Set<AnyLifetimeCancellable>()
init(dataStream: AsyncStream<String>) {
withLifetime(of: self, consuming: dataStream) { service, item in
service.items.append(item)
}
.cancellable.store(in: &cancellables)
}
}
The preferred way of installing AsyncLifetime is via the Swift Package Manager.
In Xcode:
- Open your project and navigate to File → Add Package Dependencies
- Paste the repository URL:
https://github.com/nonameplum/AsyncLifetime
- Click Next and select Up to Next Major Version
- Click Add Package
In Package.swift:
dependencies: [
.package(url: "https://github.com/nonameplum/AsyncLifetime", from: "1.0.0")
]
The most common pattern - automatic lifetime binding during initialization:
import AsyncLifetime
@MainActor
@Observable
class Model {
var items: [String] = []
private var cancellables = Set<AnyLifetimeCancellable>()
init(dataStream: AsyncStream<[String]>) {
dataStream
.assign(to: \.items, weakOn: self)
.store(in: &cancellables)
}
}
Store cancellable objects when you need explicit control:
@MainActor
@Observable
class Model {
var items: [String] = []
private var cancellables = Set<AnyLifetimeCancellable>()
init(dataStream: AsyncStream<String>) {
withLifetime(of: self, consuming: dataStream) { service, item in
service.items.append(item)
}
.cancellable.store(in: &cancellables)
}
func cancel() {
// Can manually stop all operations
cancellables.forEach { $0.cancel() }
cancellables.removeAll()
}
}
Full documentation with detailed examples and API reference is available at:
- iOS 17.0+ / macOS 14.0+ / watchOS 10.0+ / tvOS 16.0+ / visionOS 1.0+
- Swift 6.1+
- Xcode 16.0+
AsyncLifetime is released under the MIT license. See LICENSE for details.