Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

A generic type in Swift is a custom class, structure, enumeration, or actor declared with one or more type parameters. These type parameters act as placeholders for concrete types that are specified at the time of instantiation, allowing the compiler to enforce strict type safety while maintaining type-agnostic implementations.

Type Parameters

Type parameters are defined inside angle brackets (< >) immediately following the type’s name. They are conventionally named using UpperCamelCase (e.g., Element, T, Key). Once defined, the type parameter can be used throughout the type’s definition to declare properties, method parameters, and return types.
struct Container<Element> {
    var items: [Element]
    
    mutating func add(_ item: Element) {
        items.append(item)
    }
}
Multiple type parameters are separated by commas:
class Pair<T, U> {
    let first: T
    let second: U
    
    init(first: T, second: U) {
        self.first = first
        self.second = second
    }
}

Type Constraints

By default, a type parameter can accept any concrete type. Type constraints restrict the allowed types by requiring the type parameter to inherit from a specific class or conform to a specific protocol (or protocol composition). Constraints are applied directly in the type parameter list using the : syntax.
import UIKit

// 'Key' is constrained to types conforming to the 'Hashable' protocol
struct HashMap<Key: Hashable, Value> {
    var storage: [Key: Value] = [:]
}

// 'T' is constrained to inherit from 'UIViewController'
class ViewControllerWrapper<T: UIViewController> {
    let viewController: T
    
    init(viewController: T) {
        self.viewController = viewController
    }
}

Generic Implementation and Specialization

When a generic type is instantiated, concrete types replace the type parameters. Unlike languages that strictly rely on monomorphization (generating distinct code for every type), Swift uses a hybrid approach to balance performance and binary size. By default, Swift compiles a single shared implementation for a generic type. This shared code uses runtime type metadata and value witness tables to dynamically handle operations (like memory allocation, copying, and destruction) for different concrete types, which minimizes code bloat. However, as a compiler optimization, Swift performs generic specialization (monomorphization) when the concrete types are statically knowable and visible to the optimizer. In these cases, the compiler generates highly optimized, distinct machine code for the specific concrete type, eliminating the runtime overhead of dynamic dispatch.
// Depending on visibility and optimization levels, the compiler may use the 
// shared generic implementation or generate specialized machine code for Int and String.
var intContainer = Container<Int>(items: [])
var stringContainer = Container<String>(items: [])

Extending Generic Types

When extending a generic type, you do not provide the type parameter list in the extension declaration. The original type parameters from the type definition are implicitly available within the body of the extension.
extension Container {
    var topItem: Element? {
        return items.last
    }
}

Conditional Extensions

You can use a generic where clause in an extension to add functionality only when the type parameters satisfy specific conditions. This allows a generic type to gain additional methods or properties based on the capabilities of its resolved concrete type.
// This method is only available if 'Element' conforms to 'Equatable'
extension Container where Element: Equatable {
    func contains(_ item: Element) -> Bool {
        return items.contains(item)
    }
}

Generics in Protocols (Associated Types)

While classes, structs, enums, and actors use type parameters (<T>), Swift protocols handle generics differently to avoid complex type hierarchies. Protocols use associatedtype to declare a placeholder name for a type that is used as part of the protocol. The concrete type is determined by the conforming type.
protocol DataProvider {
    associatedtype DataType
    func fetch() -> DataType
}

struct StringProvider: DataProvider {
    // The compiler infers DataType == String based on the return type
    func fetch() -> String {
        return "Data"
    }
}
Master Swift with Deep Grasping Methodology!Learn More