What are lightweight generics, and what do they not do?
Predict first
What types does Swift report?
let c = Catalog()
print(type(of: c.names), type(of: c.items))
- Array<String> Array<String>
- Array<String> Array<Any>
- Array<Any> Array<Any>
Mental model. A label on a shelf that says “books only”. It helps shoppers, but nobody checks what is actually put on it.
Objective-C collections can carry an element type in the declaration: NSArray<NSString *> *, NSDictionary<NSString *, NSNumber *> *. Swift imports these as [String] and [String: NSNumber]. An unparametrised NSArray * imports as [Any], so you must cast every element yourself. You can also declare generic Objective-C classes with @interface Box<__covariant T> : NSObject, which import as generic Swift classes.
They are lightweight because the information exists only at compile time. At runtime an NSArray is still an NSArray: there is no check that the elements really are strings. The Objective-C compiler only warns on a mismatch, and Swift trusts the declaration and may crash on a bad cast when it bridges. So annotate honestly, keep arrays homogeneous, and treat data coming from untyped sources (JSON, plists) as [Any] until you validate it.
The parameterised array imports with its element type. The bare NSArray * falls back to [Any].
Common traps
- Trusting an element type from an untyped source. A stray
NSNullin a “string” array crashes the bridge. - Using unparametrised
NSArray *in new headers. Swift callers get[Any]and cast everywhere.
They might follow up with
- Does this apply to custom classes? Yes, with generic parameters on the
@interface; Swift sees a generic class. - What is
__covariantfor? It lets aBox<NSString *>be used where aBox<NSObject *>is expected.
Related questions
- 4.5.1 How do nullability annotations change what Swift sees, and what happens when they lie?
- 2.9.1 What is toll-free bridging, and how do you convert between Swift and Foundation types?
← How do nullability annotations change what Swift sees, and what… How do you make an Objective-C API feel native in Swift? →