What are lightweight generics, and what do they not do?

4.5.2 · L3 · Interview

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 NSNull in 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 __covariant for? It lets a Box<NSString *> be used where a Box<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? →