Skip to main content

Working with Extensions

1. Adding Computed Properties

Property Type Description
computedProperty Int Read-only computed property that returns the square of a base value.

Example: Adding a Computed Property to a Struct

struct Rectangle {
    var width: Double
    var height: Double
    
    var area: Double {
        return width * height
    }
}

let rect = Rectangle(width: 5.0, height: 3.0)
print(rect.area) // Output: 15.0

2. Adding Methods to Existing Types

Extension Method Description
String func reverse() -> String Reverses the characters in a string
Array<Int> func sum() -> Int Returns the sum of all elements in the array
Optional<String> func unwrap() -> String Unwraps an optional string or returns a default value

Example: Adding a reverse method to String

extension String {
    func reverse() -> String {
        return String(self.reversed())
    }
}

let greeting = "Hello"
print(greeting.reverse()) // Output: olleH

Example: Adding a sum method to Array<Int>

extension Array<Int> {
    func sum() -> Int {
        return self.reduce(0, +)
    }
}

let numbers = [1, 2, 3, 4, 5]
print(numbers.sum()) // Output: 15

3. Adding Initializers via Extensions

Property Description Example
extension Used to add new functionality to existing types
extension String {
    init(number: Int) {
        self = String(number)
    }
}

Example: Adding an initializer to a custom struct

struct Point {
    var x: Double
    var y: Double
}

extension Point {
    init(from array: [Double]) {
        self.x = array[0]
        self.y = array[1]
    }
}
Note: Extensions can add initializers, but they cannot override existing ones.

4. Adding Nested Types

Extensions in Swift can define nested types, which are useful for organizing related functionality within the extension.

Feature Description Example
Nested Type Definition Define types inside an extension using the struct, class, or enum keywords.
extension MyStruct {
    struct NestedType {
        let value: Int
    }
}
Access Control Nested types can have their own access control levels (e.g., public, internal).
extension MyStruct {
    public struct PublicNestedType {
        let value: Int
    }
}

Example: Using Nested Types in Extensions

struct MyStruct {
    var name: String
}

extension MyStruct {
    enum Status {
        case active
        case inactive
    }
    
    struct Config {
        let maxRetries: Int
    }
}

let config = MyStruct.Config(maxRetries: 3)
print(config.maxRetries) // Output: 3

5. Using Protocol Conformance Extensions

Feature Description Example
Protocol Extension Allows adding methods, properties, and initializers to a protocol.
protocol Drawable { 
    func draw() 
}

extension Drawable { 
    func drawWithBorder() { 
        print("Drawing with border") 
    } 
}
Default Implementation Provides default behavior for protocol methods.
extension Drawable { 
    func draw() { 
        print("Default drawing") 
    } 
}
Conditional Conformance Allows types to conform to a protocol only under certain conditions.
extension Array: Drawable where Element == String { 
    func draw() { 
        print("Drawing array of strings") 
    } 
}

Example: Protocol Extension with Default Implementation

protocol Shape { 
    var area: Double { get } 
    func perimeter() -> Double 
}

extension Shape { 
    func perimeter() -> Double { 
        return 0.0 
    } 
}

struct Circle: Shape { 
    let radius: Double 
    var area: Double { 
        return Double.pi * radius * radius 
    } 
}
Note: Protocol extensions can provide default implementations for methods, allowing conforming types to inherit these behaviors unless overridden.

6. Extending Generic Types

Feature Description Example
Generic Extension Syntax Extensions can be defined for generic types using the same syntax as generic functions.
extension Array where Element: Comparable {
    func sorted() -> [Element] {
        return self.sorted()
    }
}
Conditional Conformance Extensions can provide conformance to protocols only under certain conditions.
extension Array: Comparable where Element: Comparable {
    func isOrderedBefore(other: [Element]) -> Bool {
        return self < other
    }
}
Generic Constraints Use constraints to limit the types that can be extended.
extension Dictionary where Key: Hashable, Value: Equatable {
    func containsValue(_ value: Value) -> Bool {
        return self.values.contains(value)
    }
}

Example: Extending a Generic Type with Constraints

struct Stack<Element> {
    var elements: [Element] = []
    
    mutating func push(_ element: Element) {
        elements.append(element)
    }
    
    mutating func pop() -> Element? {
        return elements.popLast()
    }
}

extension Stack where Element: Equatable {
    func contains(_ element: Element) -> Bool {
        return elements.contains(element)
    }
}

7. Using Conditional Extensions

Conditional extensions in Swift allow you to conditionally extend a type based on compile-time conditions. This is particularly useful for platform-specific code or when you want to provide different implementations based on the availability of certain features.

Condition Description Usage
available Checks if a feature or platform is available at compile time. #available(iOS 13.0, *)
swift Checks the Swift language version. #if swift(<5.5)

Example: Platform-specific extension

extension String {
    #if canImport(UIKit)
    func applyFont(_ font: UIFont) -> NSAttributedString {
        return NSAttributedString(string: self, attributes: [.font: font])
    }
    #endif
}

Example: Version-specific extension

extension Array {
    #if swift(>=5.5)
    func safeElement(at index: Int) -> Element? {
        guard index >= 0 && index < self.count else { return nil }
        return self[index]
    }
    #else
    func safeElement(at index: Int) -> Element? {
        guard index >= 0 && index < self.count else { return nil }
        return self[index]
    }
    #endif
}

8. Organizing Code with Extensions

Feature Description Usage
Extension Declaration Defines a new extension for a type extension TypeName { ... }
Adding Properties Allows adding computed properties to existing types extension String { var length: Int { return self.count } }
Adding Methods Enables adding instance or type methods to existing types extension Int { func isEven() -> Bool { return self % 2 == 0 } }
Adding Initializers Allows adding initializers to existing types (only for structs and classes) extension String { init?(from data: Data) { ... } }
Adding Subscripts Enables adding subscript access to existing types extension Array { subscript(safe index: Int) -> Element? { ... } }
Conforming to Protocols Allows types to conform to new protocols via extensions extension String: Codable { ... }

Example: Adding a Method to a Built-in Type

extension String {
    func isPalindrome() -> Bool {
        let cleaned = self.lowercased().replacingOccurrences(of: " ", with: "")
        return cleaned == String(cleaned.reversed())
    }
}

let testString = "A man a plan a canal Panama"
print(testString.isPalindrome()) // true

Example: Adding a Computed Property

extension Double {
    var km: Double { return self * 1000 }
    var m: Double { return self }
    var cm: Double { return self / 100 }
}

let distance = 2.5
print(distance.km) // 2500.0
Note: Extensions can't add stored properties to types, but they can add computed properties.
Warning: Extensions cannot override methods or properties of the original type.

9. Adding Subscripts via Extensions

Feature Description Example
Subscript Declaration Defines a subscript using the subscript keyword inside an extension.
extension Array {
    subscript(index: Int) -> Int {
        return self[index]
    }
}
Multiple Parameters Subscripts can accept multiple parameters, allowing for complex indexing.
extension Dictionary {
    subscript(key: String, defaultValue: String) -> String {
        return self[key] ?? defaultValue
    }
}
Computed Properties Subscripts can return computed values based on other properties or logic.
extension String {
    subscript(range: Range<Int>) -> String {
        let start = self.index(self.startIndex, offsetBy: range.lowerBound)
        let end = self.index(self.startIndex, offsetBy: range.upperBound)
        return String(self[start..<end])
    }
}

Example: Accessing Array Elements via Extension Subscript

extension Array {
    subscript(safe index: Int) -> Element? {
        return (0 ..< self.count).contains(index) ? self[index] : nil
    }
}

let numbers = [1, 2, 3, 4, 5]
print(numbers[safe: 2]) // Optional(3)
print(numbers[safe: 10]) // nil

10. Understanding Extension Limitations

Limitation Description Impact
Cannot add stored properties Extensions cannot declare stored properties, only computed properties. Must use associatedtype or property wrappers for similar functionality.
Cannot override methods Extensions cannot override methods from the original type. Must use subclassing or protocol extensions for method overriding.
Cannot add initializers Extensions cannot declare initializers for types they extend. Must define initializers in the original type or use convenience methods.
Cannot conform to protocols Extensions cannot make a type conform to a new protocol. Must define conformance in the original type declaration.
Cannot access self in computed properties Computed properties in extensions cannot access self directly in some contexts. Use self carefully and ensure context clarity.
Note: Extensions are powerful but have strict limitations that prevent them from fundamentally altering type structure or behavior.

Was this article helpful?