Data Modeling with Structures, Classes, Enumerations, and Protocols
Choosing a type
Use a struct for independent values with value semantics: assignment and passing copy the value, although copy-on-write makes large collections cheap. Use a class when you need identity, shared mutable state, inheritance, or deinitialization. Compare class instances with === for identity and == for value equality you define. Structs get a memberwise initializer automatically; classes must define or inherit designated initializers and follow Swift's initialization order: initialize stored properties, then call super.init, then use self.
Enumerations
Enums model a closed set of cases, optionally with associated values that make each case carry data. Raw-value enums have a raw type and unique literals; initializing with init?(rawValue:) returns an optional. Recursive enums need indirect.
Protocols
A protocol declares requirements: properties (with get or get set), methods, initializers, and associated types. Conformers must satisfy them. Protocol extensions can supply default implementations; remember that defaults resolved through an extension are chosen statically, so a conformer's own implementation may not be called through a protocol-typed reference. Use protocol composition (A & B) and some or any deliberately.
Traps
- Expecting structs to inherit or to be compared by identity.
- Calling a mutating method on a
letstruct. - Assuming a protocol extension's default overrides a conformer's method at runtime.
- Forgetting that an array of structs copies on assignment, while an array of class references shares the instances.
Rule of thumb
Model data with structs and enums first; reach for classes only when identity or shared mutation is essential. Keep protocols small and focused.