iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Extensions

Extensions add functionality to existing types — including types you don’t own, like String, Array, or third-party SDKs. They’re Swift’s primary tool for organising code, conforming to protocols after the fact, and turning verbose call sites into expressive APIs.

Methods, computed props, protocols, types

EXAMPLE
// 1) Add methods to a type
extension String {
    func trimmed() -> String { trimmingCharacters(in: .whitespacesAndNewlines) }
    func isEmail() -> Bool {
        let pattern = #"^[^@\s]+@[^@\s]+\.[^@\s]+$"#
        return range(of: pattern, options: .regularExpression) != nil
    }
}

"  hello world  ".trimmed()       // "hello world"
"mara@example.com".isEmail()       // true

// 2) Computed properties
extension Int {
    var isEven: Bool { self % 2 == 0 }
    var squared: Int { self * self }
    var asCurrency: String {
        let f = NumberFormatter()
        f.numberStyle = .currency; f.currencyCode = "AUD"
        return f.string(from: NSNumber(value: self)) ?? "\$0.00"
    }
}
10.isEven           // true
10.squared          // 100
9999.asCurrency     // "\$9,999.00"

// 3) New initializers via extensions
extension URL {
    init?(safeString s: String) {
        guard let encoded = s.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { return nil }
        self.init(string: encoded)
    }
}
let u = URL(safeString: "https://example.com/search?q=swift extensions")

// 4) Conditional extensions (with where clauses)
extension Array where Element: Numeric {
    var sum: Element { reduce(0, +) }
}
extension Array where Element == String {
    func joinedByComma() -> String { joined(separator: ", ") }
}
[1, 2, 3].sum                                  // 6
["a", "b", "c"].joinedByComma()                 // "a, b, c"

// 5) Protocol conformance via extension — separate intent from data
struct User { let id: Int; let name: String }

extension User: Equatable {
    static func == (a: User, b: User) -> Bool { a.id == b.id }
}
extension User: Hashable {
    func hash(into hasher: inout Hasher) { hasher.combine(id) }
}
extension User: CustomStringConvertible {
    var description: String { "User(\(id), \(name))" }
}

// Keeping conformance in their own extension blocks signals which set of methods
// is satisfying which protocol — easier to read than one giant struct body.

// 6) Protocol extensions — default implementations
protocol Greetable {
    var name: String { get }
}

extension Greetable {
    func greet() -> String { "Hello, \(name)!" }
    func loudGreet() -> String { greet().uppercased() }
}

struct Cat: Greetable { let name: String }
Cat(name: "Mittens").greet()                    // "Hello, Mittens!"
Cat(name: "Mittens").loudGreet()                 // "HELLO, MITTENS!"
// Cat got behaviour for free by adopting the protocol.

// 7) Overriding default implementations
struct PoliteCat: Greetable {
    let name: String
    func greet() -> String { "Good day, \(name)." }
}
PoliteCat(name: "Sir").greet()                  // "Good day, Sir."

// 8) Nested types in extensions
extension UIColor {
    struct Brand {
        static let primary   = UIColor(red: 0.31, green: 0.27, blue: 0.90, alpha: 1)
        static let secondary = UIColor(red: 0.08, green: 0.72, blue: 0.65, alpha: 1)
    }
}
let c = UIColor.Brand.primary

// 9) Extending generic types
extension Optional where Wrapped == String {
    var orEmpty: String { self ?? "" }
    var isNilOrEmpty: Bool { self?.isEmpty ?? true }
}
let maybe: String? = nil
maybe.orEmpty                       // ""
maybe.isNilOrEmpty                  // true

// 10) Limits — what extensions CAN'T do
//   • Add stored properties (only computed)
//       Workaround: associated objects (Objective-C runtime, hacky)
//   • Override existing methods from outside (use subclassing or wrappers)
//   • Add @objc selectors with the same name as an existing one
//   • Declare new designated initializers for classes (only convenience inits)

// 11) Organising large types
// One file: 'Profile.swift'                     — primary type definition
// Sibling:  'Profile+Networking.swift'           — networking helpers
// Sibling:  'Profile+Persistence.swift'          — Core Data hooks
// Sibling:  'Profile+UI.swift'                    — view helpers (UIKit/SwiftUI)
// File-per-extension keeps blame logs clean and merges manageable.

// 12) Naming conventions
//   • Module-qualified when possible: 'extension Array' over 'extension Array' inside generic group
//   • Method extensions follow Swift API guidelines — start with a verb for actions, noun for property-like values
//   • Don't ship clashing extensions in a library — namespace via wrapper struct

// 13) Wrapper / 'rx-style' namespacing
struct Reactive<Base> { let base: Base }
extension UIView {
    var rx: Reactive<UIView> { Reactive(base: self) }
}
extension Reactive where Base: UIView {
    var isHidden: Binding<Bool> { /* ... */ fatalError() }
}
// button.rx.isHidden     ← scoped namespace, no name clashes with future Apple APIs

// 14) Common bugs
//   • Adding a stored property — won't compile; only computed allowed
//   • Overriding a method declared in an extension from another extension — undefined behaviour for protocol-extension dispatch
//   • Conforming the same type to the same protocol twice across modules → linker error or warning
//   • Cluttering a base type with hundreds of extension methods — split by concern
//   • Forgetting 'where Self: ...' on a protocol extension generic constraint
//   • Tight coupling: extending a UIKit type to call your domain code — keep extensions free of business logic where possible

Why it matters

Extensions are how idiomatic Swift code stays readable: keep the primary type clean, split conformances and helpers into their own extension files, and group behaviour by concern (+Networking, +UI). Just remember they can’t add stored properties — if you find yourself wanting one, refactor with composition instead.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
extension String {
    var isEmail: Bool { contains("@") }
}
print("ada@example.com".isEmail)
Try it Yourself »

Discussion

Loading…