Limitation with classes derived from generic classes in swift
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Swift generics are powerful, but subclassing a generic class introduces several restrictions that do not exist with protocols or non-generic classes. The main limitations involve Objective-C interoperability, covariance, stored property overriding, and runtime type behavior. Understanding these constraints helps you decide when to use generic class inheritance versus protocol-based composition.
Basic Generic Class and Subclass
Both patterns work, but the limitations below apply.
Limitation 1: No @objc on Generic Subclasses
Classes that inherit from a generic class cannot be exposed to Objective-C:
This means you cannot use generic subclasses as @IBAction targets, NSObject subclasses with dynamic dispatch, or @objc protocol conformances. The workaround is to wrap the generic logic in a non-generic class or use a protocol with an associated type instead.
Limitation 2: No Covariance for Generic Classes
Swift arrays are covariant ([Dog] is a subtype of [Animal]), but user-defined generic classes are invariant:
Even though Dog is a subclass of Animal, Cage<Dog> is not a subclass of Cage<Animal>. This is because Cage<T> has a settable property of type T, so allowing the assignment would break type safety (you could put a Cat into a Cage<Dog> through the Cage<Animal> reference).
The workaround is to use a protocol with an associated type or erase the type:
Limitation 3: Cannot Override Stored Properties with Different Types
A subclass of a generic class cannot override a stored property to narrow its type:
You can add new stored properties and override methods, but not replace inherited stored properties. Use computed properties or methods to customize behavior.
Limitation 4: Type Information Lost at Runtime
Generic type parameters are erased at runtime. This affects is and as checks:
For runtime type discrimination, use an enum or a protocol with concrete conformances instead of relying on generic class checks.
Limitation 5: Required Initializers and Generics
If a generic superclass defines a required initializer, every subclass must implement it — even if the subclass fixes the generic parameter:
This becomes verbose in deep hierarchies. If the initializer signature depends on T, each subclass must restate it with the concrete type.
Protocol-Based Alternative
When generic class limitations become burdensome, consider using protocols with associated types:
Protocols avoid the inheritance chain, work with @objc conformance (when the associated type is concrete), and are generally more flexible in Swift.
Common Pitfalls
- Expecting covariance:
Box<Dog>is not a subtype ofBox<Animal>. Use type erasure or a protocol to achieve polymorphism across different generic specializations. - Using generic subclasses with Interface Builder:
@IBActionand@IBOutletrequire@objc, which is unavailable on generic class subclasses. Extract the IB-connected code into a non-generic class. - Deep generic inheritance hierarchies: Each layer must forward generic parameters and required initializers, creating boilerplate. Prefer composition (hold a generic object as a property) over inheritance.
- Runtime type checks on generic parameters:
is Wrapper<Int>may not behave as expected in all contexts because Swift erases generic types at runtime. Use concrete wrapper types or enums instead. - Overriding methods with generic constraints: A subclass cannot add new constraints to an inherited method's generic parameter. Define the constraint on the superclass or use a protocol extension with a
whereclause.
Summary
- Generic class subclasses cannot be marked
@objcor used with Interface Builder - User-defined generic classes are invariant —
Box<Dog>is not a subtype ofBox<Animal> - Stored properties from generic superclasses cannot be overridden with narrower types
- Generic type parameters are erased at runtime, limiting
is/aschecks - Required initializers must be restated in every subclass with concrete type parameters
- Prefer protocols with associated types over deep generic class hierarchies when these limitations become blocking

