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 | |
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. |
|
| Access Control | Nested types can have their own access control levels (e.g., public, internal). |
|
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. | |
| Default Implementation | Provides default behavior for protocol methods. | |
| Conditional Conformance | Allows types to conform to a protocol only under certain conditions. | |
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. | |
| Conditional Conformance | Extensions can provide conformance to protocols only under certain conditions. | |
| Generic Constraints | Use constraints to limit the types that can be extended. | |
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. |
|
| Multiple Parameters | Subscripts can accept multiple parameters, allowing for complex indexing. | |
| Computed Properties | Subscripts can return computed values based on other properties or logic. | |
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.