DebugReplaceableView and Swift 6.2's Multiple Type Erasers
Swift 6.2 now supports multiple @_typeEraser attributes, enabling SwiftUI's new DebugReplaceableView for faster Xcode Previews on iOS 26+.
— Kyle Ye (@KyleSwifter) January 26, 2026
Here's how it works under the hood.https://t.co/NRypPpayYK
iOS 26 introduces DebugReplaceableView, a new type eraser for SwiftUI's View protocol designed to accelerate Xcode Previews. This required a fundamental change in the Swift compiler: support for multiple @_typeEraser attributes on a single protocol.
Background: @_typeEraser
The @_typeEraser attribute is an underscored Swift compiler feature that marks a concrete type as the official type eraser for a protocol. From the Swift documentation:
@_typeEraser(Proto) marks a concrete nominal type as one that implements type erasure for a protocol Proto.
Type eraser requirements:
- Must be a concrete nominal type (class, struct, or enum)
- Must conform to the protocol it erases
- Must have an initializer of the form
init<T: Proto>(erasing: T) - Cannot have more restrictive access than the protocol
SwiftUI has been using this with AnyView since its inception:
@_typeEraser(AnyView)
public protocol View {
associatedtype Body : View
@ViewBuilder var body: Self.Body { get }
}
When a dynamic function returns an opaque type, the compiler automatically wraps it:
import SwiftUI
// swiftc -target arm64-apple-macosx15.0 -dump-ast MyView.swift | grep -A5 "erasing" # AnyView.init(erasing:)
// swiftc -target arm64-apple-macosx26.0 -dump-ast MyView.swift | grep -A5 "erasing" # DebugReplaceableView.init(erasing:)
dynamic func makeView() -> some View {
Text("Hello")
}
Note: Type erasers are only added for dynamic functions or when the experimental feature OpaqueTypeErasure is enabled via -enable-experimental-feature OpaqueTypeErasure.
The Limitation
Prior to Swift 6.2, protocols could only have one @_typeEraser attribute. The duplicate check in lib/Parse/ParseDecl.cpp:
// Diagnose duplicated attributes.
const DeclAttribute *DuplicateAttribute = nullptr;
if (!DeclAttribute::allowMultipleAttributes(DK))
if ((DuplicateAttribute = Attributes.getAttribute(DK))) {
// Issue diagnostic for duplicate attribute
}
This constraint worked until SwiftUI needed different type erasers for different OS versions and contexts—newer platforms could support optimized debug instrumentation while maintaining backward compatibility.
Swift 6.2's Enhancement
Commit 8f706b8c
added the AllowMultipleAttributes flag to @_typeEraser:
// include/swift/AST/DeclAttr.def
DECL_ATTR(_typeEraser, TypeEraser,
OnProtocol,
UserInaccessible | ABIStableToAdd | ABIBreakingToRemove |
- APIStableToAdd | APIBreakingToRemove,
+ APIStableToAdd | APIBreakingToRemove | AllowMultipleAttributes,
94)
Availability-Based Selection
With multiple type erasers, the compiler selects based on availability. From lib/Sema/ConstraintSystem.cpp:buildTypeErasedExpr:
auto contextAvailability =
AvailabilityContext::forLocation(expr->getLoc(), dc);
auto refinedAvailability =
AvailabilityContext::forPlatformRange(
AvailabilityRange::alwaysAvailable(), ctx);
Type typeEraser;
for (auto *attr : PD->getAttrs().getAttributes<TypeEraserAttr>()) {
auto eraser = attr->getResolvedType(PD);
auto *nominal = eraser->getAnyNominal();
auto nominalAvailability = AvailabilityContext::forDeclSignature(nominal);
// Select the most restrictive type eraser that's still available
if (contextAvailability.isContainedIn(nominalAvailability) &&
nominalAvailability.isContainedIn(refinedAvailability)) {
refinedAvailability = nominalAvailability;
typeEraser = eraser;
}
}
The algorithm iterates through all type erasers and selects the most restrictive one available in the current context. This is demonstrated in the Swift test suite:
// test/Sema/type_eraser.swift
class AnyP: P {
init<T: P>(erasing: T) {}
}
@available(macOS 100, *)
struct NewAnyP: P {
init(erasing: some P) {}
}
@_typeEraser(AnyP) // Available everywhere
@_typeEraser(NewAnyP) // Available macOS 100+
protocol P {}
// macOS < 100: uses AnyP
// macOS 100+: uses NewAnyP
DebugReplaceableView
With multiple type erasers enabled, SwiftUI can now have both AnyView and DebugReplaceableView:
@_typeEraser(AnyView)
@_typeEraser(DebugReplaceableView) // iOS 26+
public protocol View {
associatedtype Body : View
@ViewBuilder var body: Self.Body { get }
}
According to Apple's documentation, DebugReplaceableView enables faster view updates in development by allowing view definitions to be replaced without full hierarchy rebuilds.
The OpenSwiftUI implementation reveals the supporting infrastructure:
Platform-Based Selection
The compiler selects type erasers based on platform availability:
| Platform | Type Eraser Selected | When Applied |
|---|---|---|
| macOS/iOS 26+ | DebugReplaceableView | dynamicfunctions or with -enable-experimental-feature OpaqueTypeErasure |
| macOS/iOS < 26 | AnyView | dynamicfunctions or with -enable-experimental-feature OpaqueTypeErasure |
The key insight: debug configurations can enable the experimental feature OpaqueTypeErasure (see swift-build#211), which applies type erasers to all opaque return types, not just dynamic functions. This enables hot-reloading in Xcode Previews.
You can verify the selection:
import SwiftUI
// swiftc -target arm64-apple-macosx26.0 -dump-ast MyView.swift | grep -A5 "erasing" # No result
// swiftc -target arm64-apple-macosx26.0 -enable-experimental-feature OpaqueTypeErasure -dump-ast MyView.swift | grep -A5 "erasing" # DebugReplaceableView.init(erasing:)
func makeView() -> some View {
Text("Hello")
}
Impact
The practical effect: faster Xcode Previews on macOS/iOS 26+ without any code changes. When you modify a view, only that specific view gets hot-swapped rather than rebuilding the entire hierarchy.
This optimization is automatic—no source changes needed. The compiler selects the appropriate type eraser based on platform availability. On newer platforms (26+), DebugReplaceableView provides enhanced debugging capabilities, while older platforms continue using AnyView.
Conclusion
The addition of multiple type erasers to Swift 6.2 enables platform-specific optimizations without API changes. DebugReplaceableView demonstrates this capability by providing enhanced debugging on newer platforms while maintaining backward compatibility.
This pattern opens possibilities for future enhancements—different type erasers for different platforms, optimization levels, or use cases—all selected automatically by the compiler based on availability. The combination with experimental features like OpaqueTypeErasure shows how compiler features can be progressively adopted without breaking existing code.
References
- Swift commit 8f706b8c
- Multiple type erasers implementation - OpenSwiftUI PR #747
- DebugReplaceableView - Apple Documentation
- DebugReplaceableView - Swift Underscored Attributes
- @_typeEraser reference - swift-build#211
- OpaqueTypeErasure experimental feature