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…