Sometimes you want a type to do something it doesn't do yet: check whether a String looks like an email, format a Double as money, or get the second item of an Array. You can't edit String's source code, but Swift lets you extend it.
An extension adds new functionality to an existing type: methods, computed properties, initializers, nested types and protocol conformances. It works on your own types and on Apple's, from Int and String to UIColor and Date. Once added, the new functionality looks exactly like it was always part of the type: "ada@example.com".isEmail.
Extensions are also an organisational tool. Swift developers often split a type's code into several extensions, one per protocol it adopts, which keeps large types easy to navigate. In this lesson you'll extend built-in and custom types, add protocol conformance, write extensions that only apply in certain cases, and learn their limits.
Extending a type
extension String {
var isEmail: Bool {
contains("@") && contains(".")
}
func shout() -> String { uppercased() + "!" }
}
"ada@example.com".isEmail // true
"hello".shout() // HELLO!- Inside the extension,
selfis the value the method was called on, and it can usually be left out. - Extensions can add computed properties but not stored ones, because they can't change how much memory a value uses.
- Methods that change a struct value, such as
IntorString, must bemutating, just like in the original type.
Initializers and nested types
Extensions can add convenience initializers. On a struct, adding an init in an extension keeps the automatic memberwise initializer too:
struct Money { let pence: Int }
extension Money {
init(pounds: Double) { self.pence = Int((pounds * 100).rounded()) }
}
Money(pence: 1999) // still available
Money(pounds: 19.99) // newExtensions can also add nested types, such as an enum of options that belongs to the type, and static members like static let zero = Money(pence: 0).
Protocol conformance in extensions
A common Swift style is to add each protocol conformance in its own extension:
struct Product { let name: String; let price: Double }
extension Product: CustomStringConvertible {
var description: String { "\(name) £\(price)" }
}
extension Product: Comparable {
static func < (a: Product, b: Product) -> Bool { a.price < b.price }
}This keeps related code together and makes it obvious which code exists to satisfy which protocol. It also lets you make types you don't own conform to your protocols.
Constrained extensions
A where clause limits an extension to certain cases:
extension Array where Element == Int {
var total: Int { reduce(0, +) }
}
extension Collection where Element: Comparable {
var isSorted: Bool { zip(self, dropFirst()).allSatisfy { $0 <= $1 } }
}[1, 2, 3].total works, but ["a"].total doesn't exist, because the extension only applies to arrays of Ints. Extending a protocol like Collection gives the method to arrays, sets, strings and more at once.
A safe subscript for every collection
A real-world extension you'll see in many projects adds a subscript that returns nil instead of crashing when a position doesn't exist:
extension Collection {
subscript(safe index: Index) -> Element? {
indices.contains(index) ? self[index] : nil
}
}
let colours = ["red", "green"]
print(colours[safe: 1] ?? "none") // green
print(colours[safe: 5] ?? "none") // noneExtensions can add subscripts as well as methods and properties. Because this one extends the Collection protocol rather than Array, it works on arrays, array slices and other collections too. The safe: label makes the different behaviour obvious at the call site, so ordinary colours[5] still crashes where you'd expect it to.
Methods that take closures
Extensions often add small helpers that take a closure, which can make code read naturally:
extension Int {
func times(_ action: () -> Void) {
for _ in 0..<self { action() }
}
}
3.times { print("Hip hip!") }Here self is the number the method was called on. Use helpers like this sparingly, though: a team member reading 3.times has to find the extension to know what it does, while for _ in 0..<3 is understood by every Swift developer.
Swift Extensions Syntax
Extension forms
extension Int {
var isEven: Bool { self % 2 == 0 }
func times(_ action: () -> Void) { for _ in 0..<self { action() } }
}
extension Double {
var asPounds: String { String(format: "£%.2f", self) }
}
extension MyType: Equatable { }
extension Array where Element == Double {
var average: Double { isEmpty ? 0 : reduce(0, +) / Double(count) }
}Swift Extensions Summary
extension Type { }adds methods, computed properties, initializers and nested types to any type.- Extensions can't add stored properties or override existing behaviour.
- Initializers added in an extension keep a struct's memberwise initializer.
- Add protocol conformances in separate extensions to organise code.
whereclauses constrain an extension to certain element types or protocols.
Swift Extensions Examples
Each example shows the code, the output it produces and what each part does.
Extending String and Int
extension String {
var isEmail: Bool {
guard let at = firstIndex(of: "@") else { return false }
let domain = self[index(after: at)...]
return at != startIndex && domain.contains(".") && !hasSuffix(".")
}
var initials: String {
split(separator: " ").compactMap { $0.first?.uppercased() }.joined()
}
}
extension Int {
var isEven: Bool { self % 2 == 0 }
func times(_ action: () -> Void) {
for _ in 0..<self { action() }
}
}
print("ada@example.com".isEmail, "ada@example".isEmail)
print("ada lovelace byron".initials)
print(7.isEven)
3.times { print("Hip hip, hooray!") }true false
ALB
false
Hip hip, hooray!
Hip hip, hooray!
Hip hip, hooray!Line-by-line explanation
- Lines 3 to 5Find the @, look at everything after it, and check the shape of the address.
- Lines 8 to 10Take the first letter of each word, uppercase it and join the letters.
- Line 14Inside an Int extension, self is the number.
- Lines 15 to 17A method that takes a closure and runs it self times.
Conformances in extensions
struct Product {
let name: String
let price: Double
}
extension Product: CustomStringConvertible {
var description: String { "\(name) (£\(price))" }
}
extension Product: Comparable {
static func < (a: Product, b: Product) -> Bool { a.price < b.price }
}
extension Product {
static let sample = [Product(name: "Pen", price: 1.2),
Product(name: "Lamp", price: 24.5),
Product(name: "Mug", price: 8.5)]
}
print(Product.sample.sorted())
print(Product.sample.max()!)[Pen (£1.2), Mug (£8.5), Lamp (£24.5)]
Lamp (£24.5)Line-by-line explanation
- Lines 6 to 8One extension per protocol keeps the code organised.
- Lines 10 to 12Comparable needs only <; Swift writes == automatically for this struct.
- Lines 14 to 18A static property in an extension holds sample data, handy for previews and tests.
A constrained extension
extension Array where Element == Double {
var average: Double {
isEmpty ? 0 : reduce(0, +) / Double(count)
}
}
extension Collection where Element: Comparable {
var isSorted: Bool {
zip(self, dropFirst()).allSatisfy { $0 <= $1 }
}
}
let temps = [18.5, 21.0, 19.5]
print(temps.average)
print(temps.isSorted, temps.sorted().isSorted)
print("abc".isSorted, "cab".isSorted)19.666666666666668
false true
true falseLine-by-line explanation
- Line 1average only exists on arrays of Doubles.
- Lines 7 to 11Extending Collection gives isSorted to arrays, strings and other collections at once.
- Line 9zip pairs each item with the next one; allSatisfy checks every pair is in order.
- Line 16Strings are collections of Characters, which are Comparable.
Common Swift Extensions Mistakes
Adding a stored property
extension String {
var tag: String = "" // error: extensions must not contain stored properties
}extension String {
var tag: String { "#" + lowercased().replacingOccurrences(of: " ", with: "") }
}Forgetting mutating
extension Int {
func double() { self *= 2 } // error: left side of mutating operator isn't mutable
}extension Int {
mutating func double() { self *= 2 }
}Overriding existing behaviour
extension String {
func uppercased() -> String { "nope" } // confusing: shadows the real method in this module
}extension String {
func shouted() -> String { uppercased() + "!" }
}Use new, descriptive names rather than reusing existing ones.
Swift Extensions in Real Projects
Extensions in iOS projects
Real apps are full of extensions: Color+Brand.swift defines the app's colours, Date+Formatting.swift formats dates consistently, and view controllers adopt table view protocols in separate extensions. SwiftUI previews often use a static sample property added in an extension, like the Product example above, so every preview uses the same realistic data.
Swift Extensions FAQ
Can a Swift extension add a stored property?
No. Extensions can add computed properties, methods, initializers, subscripts and nested types, but not stored properties, because those would change the size of every value of the type. Use a computed property, or add the stored property to the original type if you own it.
Can I extend a type I didn't write, like String or Int?
Yes. Extensions work on any type, including standard library types such as String, Int and Array, and framework types. You can add methods and properties and make them conform to your own protocols.
What is the difference between an extension and a subclass?
A subclass creates a new class with its own name that inherits from a parent, and only works for classes. An extension adds behaviour to the existing type itself, for every value of it everywhere it's visible, and works with structs, enums, classes and protocols.
Can an extension override an existing method?
No. Extensions add functionality but can't override. For your own types, repeating a method that already exists gives an "invalid redeclaration" error. For types from another module, such as String, a same-named method only shadows the original inside your module, which is confusing, so choose a new name instead.
What does where mean in a Swift extension?
where restricts the extension to types that meet a condition. extension Array where Element == Int applies only to arrays of Ints, and extension Collection where Element: Comparable applies only when the items can be compared.
Swift Extensions Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the code so every Int gets an isPositive property.
Int {
var isPositive: Bool { self > 0 }
}
What does this print?
extension Int { var squared: Int { self * self } }
print(6.squared)
What can't an extension add?
What does this print?
extension Array where Element == Int { var total: Int { reduce(0, +) } }
print([4, 5, 6].total)
Why add an initializer to a struct in an extension rather than in the struct itself?
Swift Extensions Quiz
5 questions to check what you learned.
Can you extend types you didn't write, such as String?
- No
- Only classes
- Yes
- Only with a subclass
What does a
whereclause do on an extension?- Renames the type
- Limits the extension to cases that meet a condition, such as Element == Int
- Makes it private
- Adds stored properties
Why add each protocol conformance in its own extension?
- It's required
- It's faster at run time
- It hides the protocol
- It keeps related code together and makes the type easier to read
What must a method in an Int extension that changes self be marked?
- mutating
- static
- override
- final
What's a common file naming convention for extensions?
- Extensions.swift for everything
- Type+Feature.swift, such as String+Validation.swift
- ext_string.swift
- StringExtensionsFile.swift