Swift has two fundamental categories of types based on how they are stored and passed. Value types, which include structs, enums, and tuples, are copied when assigned or passed into a function, so each copy is independent and modifications to one do not affect the original. Reference types, which include classes, share a single instance through references, so modifying the instance through one reference is visible to all other references. This difference influences when each type is appropriate.
A struct is a value type that encapsulates related properties and methods. Structs automatically receive a memberwise initializer that accepts each stored property as a parameter, and they are copied on assignment. When a method on a struct or enum needs to modify its own properties, it must be marked with the mutating keyword; without it, the compiler prevents any property changes. Apple recommends structs as the default choice in Swift because of their value semantics, simplicity, and inherent thread safety through copying. Classes, by contrast, are reference types that support inheritance, deinitializers, and reference counting, and they are best used when reference semantics, inheritance, or shared mutable state is needed. Swift's standard collections like Array, Dictionary, and Set use a copy-on-write optimization, where the underlying storage is shared between copies until one copy is mutated, at which point a true copy is made, preserving the performance benefits of value semantics.
Enums in Swift are far more powerful than in many other languages. Each case can carry associated values of any type, allowing enums to model states with additional context, such as a network result carrying either success data or an error. Associated values are extracted through pattern matching with a switch statement, where case .success(let data) binds the data for use in that branch. The if case let syntax provides a similar capability for checking a single case. Raw values, by contrast, are fixed, compile-time constants of the same type assigned to each case, useful for cases like case north = "N" in a string-backed enum. A single enum cannot use both raw values and associated values at the same time, since raw values are static while associated values are runtime data that can differ per instance.