Swift gates new APIs behind availability checks at the call site and availability attributes at
the declaration. #available branches at runtime; @available documents the contract to the
compiler.
Checking at the Call Site
#available runs the new branch when the platform supports it; #unavailable flips the test.
if #available(iOS 16, *) {
// Use new APIs
} else {
// Use old APIs
}
if #unavailable(iOS 16) {
// Use old APIs
}
Declaring Availability
@available marks the platforms a declaration needs. This is the pattern for backporting: the
wrapper is available early and branches to the real API when it exists.
extension View {
@available(iOS 14, macOS 11, *)
func backportedTask<Value: Equatable>(
id: Value,
task: @Sendable @escaping () async -> Void
) -> some View {
if #available(iOS 15, macOS 12, *) {
return self.task(id: id, task)
} else {
return self.onChange(of: id) { _ in
Task { await task() }
}
}
}
}
Deprecation Lifecycle
The long form tracks a lifecycle per platform. A deprecated version produces a compiler
warning at that target; an obsoleted version produces a compiler error. message adds text to
the diagnostic, and renamed gives Xcode a fix-it that rewrites the call site.
@available(iOS, introduced: 14, deprecated: 16, obsoleted: 17, renamed: "task")
@available(macOS, introduced: 11, deprecated: 12, obsoleted: 13)
Back Deployment
@backDeployed, from Swift 5.8, ships a fallback implementation to older OS versions while the
new OS uses its own copy. Library authors use it so callers get one API name on every version.
extension View {
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
@backDeployed(before: iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0)
func task<Value: Equatable>(
id: Value,
closure: @Sendable @escaping () async -> Void
) -> some View {
self.onChange(of: id) { _ in
Task { await closure() }
}
}
}