Hello everyone,
In a large iOS codebase, I recently investigated a binary size regression caused by replacing an immutable struct backed by private reference storage with an equivalent immutable final class. I was able to isolate the increase to Objective-C bridging paths emitted for standard library collections containing the class type. The module is pure Swift and these values are not intentionally bridged to Objective-C.
Minimal example
I compared two immutable, read-only data models:
struct Foo: Hashable, Equatable {
private final class Storage: Hashable, Equatable {
let key: String
let value: String
// init, hash(into:), == ...
}
private var storage: Storage
// accessors forwarding to storage...
}
and
final class Foo: Hashable, Equatable {
let key: String
let value: String
// init, hash(into:), == ...
}
The final class version produced additional binary size from Objective-C bridging-related symbols.
The main examples I found were:
-
For Array, around +4,120 bytes per case coming from:
_ArrayBuffer._getElementSlowPath(Int) -> AnyObject -
For Set, around +10,237 bytes per case, driven by Set specializations including:
NativeSet.init(:__cocoaSet:capacity:)
My understanding is that Swift classes implicitly conform to AnyObject, and Array / Set are bridgeable to NSArray / NSSet. As a result, the compiler emits bridging fallback paths for each distinct class type used in these collections, even when the code is otherwise pure Swift.
Using ContiguousArray avoids part of this tax, but replacing [T] with ContiguousArray across a large codebase is not realistic for us due to API boundary friction, inference issues, and Apple framework APIs that require standard arrays.
Questions
- Is this expected behavior for pure-Swift final class types used inside standard collections?
- Is there any existing attribute, compiler flag, or design pattern that allows a class to opt out of Objective-C bridging / AnyObject legacy behavior when it is known to be Swift-only?
- Is this theoretically dead-strippable by the optimizer or linker when no Foundation bridging entry points are reachable from the module?
- Are there known workarounds other than avoiding classes or replacing standard collections with
ContiguousArray?
Thanks - I would appreciate any context from the compiler/runtime side.