Working with Extensions, Computed Properties, and Property Wrappers in Swift
Course: iOS App Development Training
Topic: Working with Extensions, Computed Properties, and Property Wrappers
Course Link: iOS App Development Training Course
Demo Link: Register for a Course Demo
Website: JustAcademy Official Website
1. Introduction
Swift provides several features that help developers organize code, calculate values dynamically, and manage property behavior efficiently. Three important features are extensions, computed properties, and property wrappers.
Extensions allow developers to add functionality to existing types without modifying their original declarations. Computed properties calculate their values when accessed instead of storing a separate value. Property wrappers encapsulate reusable logic that controls how properties are stored, accessed, or updated.
These features are widely used in iOS development, including SwiftUI interfaces, model types, validation logic, formatting, and state management.
Learning Objectives
- Understand the purpose and syntax of Swift extensions.
- Add methods, computed properties, initializers, and protocol conformance through extensions.
- Create read-only and read-write computed properties.
- Understand property observers and distinguish them from computed properties.
- Create custom property wrappers using
@propertyWrapper.
- Use property wrappers for validation, formatting, and reusable property behavior.
- Apply these concepts to practical iOS application examples.
2. Understanding Extensions in Swift
An extension adds functionality to an existing type, such as a class, structure, enumeration, or protocol. It can add computed properties, methods, initializers, subscripts, nested types, and protocol conformance.
Extensions are useful when you want to organize related functionality separately or add capabilities to a type whose original source code you cannot modify.
Basic Syntax
extension TypeName {
// Additional functionality
}
Example: Extending a String
extension String {
func greetingMessage() -> String {
return "Hello, \(self)!"
}
}
let name = "Aarav"
print(name.greetingMessage())
Output:
Hello, Aarav!
The extension adds a new method to String. The original type declaration does not need to be changed.
3. Adding Methods Through Extensions
Extensions can add instance methods and type methods. This helps group additional operations near the type they relate to without making the original declaration unnecessarily large.
Example: Adding a Number Utility
extension Int {
func isEven() -> Bool {
return self % 2 == 0
}
}
print(8.isEven())
print(7.isEven())
Output:
true
false
Example: Adding a Type Method
struct Temperature {
var celsius: Double
}
extension Temperature {
static func freezingPoint() -> Temperature {
return Temperature(celsius: 0)
}
}
let freezing = Temperature.freezingPoint()
print(freezing.celsius)
Output:
0.0
Use extensions for closely related utility behavior. Avoid adding unrelated methods simply because an extension makes them technically possible.
4. Adding Computed Properties Through Extensions
Swift extensions can add computed properties to an existing type. However, extensions cannot add stored properties to an existing type.
extension Double {
var asMinutes: Double {
return self / 60
}
}
let seconds = 180.0
print(seconds.asMinutes)
Output:
3.0
The asMinutes property calculates its value when accessed. It does not store a separate minutes value.
Important Restriction
// This is not allowed in an extension:
// extension String {
// var customValue: Int = 0
// }
Extensions cannot add stored instance properties or property observers to existing stored properties. Use computed properties or another suitable design when additional derived behavior is required.
5. Protocol Conformance Through Extensions
Extensions can make an existing type conform to a protocol. This is particularly useful for separating protocol-specific functionality from the main type declaration.
protocol SummaryProviding {
func summary() -> String
}
struct Course {
let title: String
let duration: Int
}
extension Course: SummaryProviding {
func summary() -> String {
return "\(title) - \(duration) hours"
}
}
let course = Course(title: "iOS Development", duration: 40)
print(course.summary())
Output:
iOS Development - 40 hours
This approach keeps the primary model declaration simple while grouping protocol conformance in a dedicated extension.
6. Initializers in Extensions
Extensions can add convenience initializers to classes and initializers to structures and enumerations, subject to Swift's initialization rules.
struct User {
let firstName: String
let lastName: String
}
extension User {
init(fullName: String) {
let parts = fullName.split(separator: " ", maxSplits: 1)
self.init(
firstName: String(parts.first ?? ""),
lastName: parts.count > 1 ? String(parts[1]) : ""
)
}
}
let user = User(fullName: "Riya Sharma")
print(user.firstName)
print(user.lastName)
Output:
Riya
Sharma
For structures, an initializer added in an extension must follow Swift's rules for delegating to the available initializers. Extensions cannot add a designated initializer to a class; they can add convenience initializers that delegate to an existing designated initializer.
7. What Are Computed Properties?
A computed property calculates its value whenever it is accessed. Unlike a stored property, it does not maintain its own independent storage for the computed result.
Computed properties are useful when a value can be derived from other properties, such as a full name from first and last names, a rectangle's area from its dimensions, or a formatted label from a numeric value.
Basic Syntax
var propertyName: DataType {
get {
// Return a calculated value
}
}
Example: Calculating Rectangle Area
struct Rectangle {
var width: Double
var height: Double
var area: Double {
return width * height
}
}
let rectangle = Rectangle(width: 10, height: 5)
print(rectangle.area)
Output:
50.0
Every access to area calculates the result from the current width and height.
8. Stored Properties vs. Computed Properties
| Feature |
Stored Property |
Computed Property |
| Storage |
Stores a value as part of the instance or type. |
Calculates a value when accessed. |
| Value source |
Holds an assigned or initialized value. |
Derives its value from other data or logic. |
| Getter |
Not written explicitly for ordinary stored properties. |
Uses a getter to provide its value. |
| Setter |
Can be assigned if declared as mutable. |
Can calculate or update underlying values if a setter is provided. |
| Typical use |
Name, age, price, or selected status. |
Full name, area, total, or formatted text. |
9. Read-Only Computed Properties
A read-only computed property provides a calculated value but cannot be assigned through that property. Swift allows a shorter syntax when only a getter is needed.
struct Circle {
var radius: Double
var area: Double {
Double.pi * radius * radius
}
var circumference: Double {
2 * Double.pi * radius
}
}
let circle = Circle(radius: 4)
print(circle.area)
print(circle.circumference)
Output:
50.26548245743669
25.132741228718345
Read-only computed properties are useful for derived values that should not be directly assigned by callers.
10. Read-Write Computed Properties
A read-write computed property includes both a getter and a setter. The getter returns a derived value, while the setter updates one or more underlying stored properties.
struct TemperatureConverter {
var celsius: Double
var fahrenheit: Double {
get {
(celsius * 9 / 5) + 32
}
set {
celsius = (newValue - 32) * 5 / 9
}
}
}
var temperature = TemperatureConverter(celsius: 0)
print(temperature.fahrenheit)
temperature.fahrenheit = 212
print(temperature.celsius)
Output:
32.0
100.0
The special name newValue represents the value assigned to the computed property. In the setter, it is converted back to Celsius and stored in the underlying property.
11. Custom Setter Parameter Names
Swift lets you provide a name for the value passed to a computed property's setter. This can make the implementation more descriptive than the default newValue.
struct Score {
var points: Int
var doubledPoints: Int {
get {
points * 2
}
set(updatedValue) {
points = updatedValue / 2
}
}
}
var score = Score(points: 10)
score.doubledPoints = 50
print(score.points)
print(score.doubledPoints)
Output:
25
50
Choose a setter parameter name that clearly communicates the value being assigned.
12. Computed Properties with Multiple Stored Properties
A computed property can use multiple stored properties to calculate a single result.
struct Product {
var price: Double
var quantity: Int
var discountPercentage: Double
var subtotal: Double {
price * Double(quantity)
}
var discountAmount: Double {
subtotal * discountPercentage / 100
}
var finalPrice: Double {
subtotal - discountAmount
}
}
let product = Product(
price: 200,
quantity: 3,
discountPercentage: 10
)
print(product.subtotal)
print(product.discountAmount)
print(product.finalPrice)
Output:
600.0
60.0
540.0
In financial applications, use appropriate currency and rounding strategies instead of relying on binary floating-point calculations for exact monetary values.
13. Property Observers: willSet and didSet
Property observers allow code to run when the value of a stored property is about to change or has changed. The two observers are willSet and didSet.
- willSet: Runs immediately before the stored property's value changes and provides the incoming value.
- didSet: Runs immediately after the stored property's value changes and can access the previous value through
oldValue.
struct DownloadManager {
var progress: Int = 0 {
willSet {
print("New progress: \(newValue)%")
}
didSet {
print("Previous progress: \(oldValue)%")
}
}
}
var manager = DownloadManager()
manager.progress = 50
Output:
New progress: 50%
Previous progress: 0%
Property observers are appropriate when a stored property's change needs to trigger an action, such as logging, updating related state, or notifying another component.
14. Computed Properties vs. Property Observers
| Feature |
Computed Property |
Property Observer |
| Main purpose |
Calculates or provides a value. |
Responds to changes in a stored property. |
| Key components |
get and optional set. |
willSet and/or didSet. |
| Own storage |
Does not store a separate computed result. |
Observes an underlying stored property. |
| Typical use |
Derived values and conversions. |
Logging, side effects, and change handling. |
15. Introduction to Property Wrappers
A property wrapper encapsulates reusable property-management logic in a separate type. It is declared using the @propertyWrapper attribute and typically defines a wrappedValue property.
Property wrappers are helpful when the same behavior must be applied to several properties, such as trimming text, validating input, storing settings, or managing UI state.
Basic Syntax
@propertyWrapper
struct ExampleWrapper {
var wrappedValue: String
}
The wrappedValue is the value exposed to code that uses the wrapped property. The wrapper can add logic to the getter and setter to control how that value is read or updated.
16. Creating a Custom Property Wrapper
The following example creates a property wrapper that trims whitespace whenever a string value is assigned.
@propertyWrapper
struct Trimmed {
private var value: String = ""
var wrappedValue: String {
get {
value
}
set {
value = newValue.trimmingCharacters(
in: .whitespacesAndNewlines
)
}
}
init(wrappedValue: String) {
self.wrappedValue = wrappedValue
}
}
struct UserProfile {
@Trimmed var username: String = ""
}
var profile = UserProfile()
profile.username = " swift_user "
print("[\(profile.username)]")
Output:
[swift_user]
The wrapper ensures that whitespace is removed whenever a new username is assigned. This avoids repeating the same trimming logic in every model or form field that needs it.
17. Property Wrappers for Value Validation
A property wrapper can enforce a rule whenever a property is updated. For example, a rating may need to stay between zero and five.
@propertyWrapper
struct ClampedRating {
private var value: Int
var wrappedValue: Int {
get {
value
}
set {
value = min(max(newValue, 0), 5)
}
}
init(wrappedValue: Int) {
value = min(max(wrappedValue, 0), 5)
}
}
struct Review {
@ClampedRating var rating: Int = 0
}
var review = Review()
review.rating = 8
print(review.rating)
review.rating = -2
print(review.rating)
Output:
5
0
The wrapper clamps the assigned value to the permitted range. In a real application, consider whether invalid input should be clamped, rejected, or reported to the user. Silent correction is not appropriate for every business rule.
18. Property Wrappers and projectedValue
A property wrapper can expose additional functionality through a property named projectedValue. The projected value is accessed using the dollar-sign form, such as $username.
@propertyWrapper
struct TrackedValue {
private var value: String
private(set) var wasChanged = false
var wrappedValue: String {
get {
value
}
set {
value = newValue
wasChanged = true
}
}
var projectedValue: Bool {
wasChanged
}
init(wrappedValue: String) {
value = wrappedValue
}
}
struct Settings {
@TrackedValue var theme: String = "Light"
}
var settings = Settings()
print(settings.$theme)
settings.theme = "Dark"
print(settings.$theme)
Output:
false
true
The projected value can expose metadata or helper behavior that is separate from the main property value. Its type and behavior are determined by the wrapper's implementation.
19. Property Wrappers in SwiftUI
SwiftUI uses property wrappers to connect view properties with state, data flow, and environment values. Common examples include @State, @Binding, @StateObject, @ObservedObject, and @Environment, with the appropriate choice depending on the application's data model and deployment requirements.
| Property Wrapper |
Common Purpose |
@State |
Stores view-local state owned by a view. |
@Binding |
Provides read and write access to state owned elsewhere. |
@StateObject |
Creates and manages an observable reference model owned by a view in the relevant SwiftUI lifecycle. |
@ObservedObject |
Observes an externally supplied observable object using the Combine-based observation model. |
@Environment |
Reads values supplied through the SwiftUI environment. |
@AppStorage |
Connects a property to a value stored in UserDefaults. |
SwiftUI APIs evolve across releases. Choose the observation tools appropriate for your target iOS version and model architecture.
Example: Using @State
import SwiftUI
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 16) {
Text("Count: \(count)")
Button("Increase") {
count += 1
}
Button("Reset") {
count = 0
}
}
.padding()
}
}
The @State wrapper allows SwiftUI to track changes to the local count value and update the view when the state changes.
20. Combining Extensions and Computed Properties
Extensions and computed properties work well together when you want to add derived functionality to an existing type without changing its original declaration.
struct Person {
var firstName: String
var lastName: String
}
extension Person {
var fullName: String {
"\(firstName) \(lastName)"
}
var initials: String {
"\(firstName.first ?? Character(" "))\(lastName.first ?? Character(" "))"
.uppercased()
}
}
let person = Person(firstName: "Aarav", lastName: "Mehta")
print(person.fullName)
print(person.initials)
Output:
Aarav Mehta
AM
For simpler and more reliable code, the initials property can also be implemented using local variables for each initial before combining them. Computed properties should remain easy to understand and avoid expensive or unexpected side effects.
21. Common Mistakes and Troubleshooting
| Problem |
Cause |
Solution |
| Adding a stored property in an extension |
Swift extensions cannot add stored properties. |
Use a computed property or redesign the type to own the required storage. |
| Computed property cannot be assigned |
The property has only a getter. |
Add a setter if assigning through the property is appropriate. |
| Recursive computed property |
A getter or setter refers to itself instead of an underlying value. |
Use a separate stored property for the backing value. |
| Incorrect setter result |
The conversion or assignment logic is wrong. |
Check how newValue is transformed and stored. |
| Observer does not run as expected |
The change occurs during initialization or the code assumes an observer works like a computed property. |
Review Swift's initialization rules and distinguish observers from computed properties. |
| Property wrapper rejects a value |
The wrapper enforces a rule or transforms incoming values. |
Check the wrapper's validation logic and initializer. |
| Unexpected dollar-sign behavior |
The wrapper does not define the projected value expected by the caller. |
Declare and use projectedValue where required. |
| Too much logic in a wrapper |
The wrapper has multiple unrelated responsibilities. |
Keep wrappers small and focused on one reusable property behavior. |
22. Best Practices
- Use extensions to organize related functionality and protocol conformance.
- Keep extensions focused on a clear responsibility.
- Use computed properties for values derived from existing data.
- Avoid placing expensive operations or unexpected side effects inside computed property getters.
- Use setters only when updating the underlying state through the computed property makes sense.
- Use
willSet and didSet when reacting to changes in stored properties.
- Create property wrappers when the same property-management logic is reused across multiple declarations.
- Document whether a property wrapper transforms, clamps, validates, or rejects values.
- Test wrapper initialization as well as subsequent assignments.
- Use built-in SwiftUI property wrappers when they fit the application's state-management needs.
- Do not use property wrappers to hide complex business logic that belongs in a model or service layer.
23. Practical Exercises
Exercise 1: Extend String
Create a String extension that adds a computed property returning the number of characters in the string.
Exercise 2: Rectangle Computed Properties
Create a rectangle structure with width and height, then add computed properties for area and perimeter.
Exercise 3: Temperature Conversion
Create a temperature structure with a stored Celsius value and a read-write Fahrenheit computed property.
Exercise 4: Property Observer
Create a download progress property that prints a message when its value changes.
Exercise 5: Custom Property Wrapper
Build a property wrapper that converts assigned text to uppercase. Test its behavior during initialization and assignment.
24. Mini Project: User Profile and Settings Manager
Create a small user profile model that combines extensions, computed properties, and property wrappers.
Project Requirements
- Store the user's first name and last name.
- Add a computed full name using an extension.
- Use a property wrapper to trim whitespace from a username.
- Use a property observer to report changes to the selected theme.
- Print the user's formatted profile information.
@propertyWrapper
struct CleanText {
private var value: String = ""
var wrappedValue: String {
get {
value
}
set {
value = newValue.trimmingCharacters(
in: .whitespacesAndNewlines
)
}
}
init(wrappedValue: String) {
self.wrappedValue = wrappedValue
}
}
struct UserProfile {
var firstName: String
var lastName: String
@CleanText var username: String = ""
var theme: String = "Light" {
didSet {
print("Theme changed to \(theme)")
}
}
}
extension UserProfile {
var fullName: String {
"\(firstName) \(lastName)"
}
func displayProfile() {
print("Name: \(fullName)")
print("Username: \(username)")
print("Theme: \(theme)")
}
}
var profile = UserProfile(
firstName: "Anaya",
lastName: "Kapoor",
username: " anaya_k "
)
profile.displayProfile()
profile.theme = "Dark"
Expected Output:
Name: Anaya Kapoor
Username: anaya_k
Theme: Light
Theme changed to Dark
This project demonstrates how extensions, computed properties, property wrappers, and property observers can work together. It can be expanded with profile validation, persisted settings, and a SwiftUI form.
25. Interview Questions and Answers
Q1. What is an extension in Swift?
An extension adds functionality to an existing type without changing its original source declaration.
Q2. Can extensions add stored properties?
No. Swift extensions cannot add stored properties to an existing type, although they can add computed properties.
Q3. What is a computed property?
A computed property calculates its value when accessed instead of storing a separate value.
Q4. What is the difference between read-only and read-write computed properties?
A read-only computed property has a getter only. A read-write computed property has both a getter and a setter.
Q5. What are willSet and didSet?
They are property observers. willSet runs before a stored property changes, while didSet runs after the change.
Q6. What is a property wrapper?
A property wrapper encapsulates reusable logic that manages how a property is accessed, stored, or updated.
Q7. Which attribute declares a property wrapper?
The @propertyWrapper attribute declares a custom property wrapper type.
Q8. What is wrappedValue?
wrappedValue is the primary value exposed by a property wrapper to the property that uses it.
Q9. What is projectedValue?
projectedValue exposes additional wrapper functionality or metadata, commonly accessed through the dollar-sign form.
Q10. How are extensions useful in iOS development?
They organize utility methods, add computed properties, and separate protocol conformance from the main type declaration.
Q11. Can a computed property have a setter?
Yes. A read-write computed property can use a setter to update underlying stored properties.
Q12. What is the difference between a property wrapper and a property observer?
A property wrapper encapsulates reusable property behavior in a separate type, while a property observer reacts to changes in a particular stored property.
Q13. Name some SwiftUI property wrappers.
Common examples include @State, @Binding, @StateObject, @ObservedObject, @Environment, and @AppStorage.
Q14. Can extensions add protocol conformance?
Yes. A type can adopt a protocol in an extension and implement the required functionality there.
Q15. When should a custom property wrapper be created?
Create one when the same property-management behavior is needed repeatedly and can be expressed clearly as a reusable abstraction.
26. Quick Revision Checklist
- Understand extension syntax and supported declarations.
- Know that extensions cannot add stored properties.
- Add methods and computed properties to existing types.
- Use extensions to organize protocol conformance.
- Understand computed property getters and setters.
- Differentiate stored properties, computed properties, and property observers.
- Use
willSet and didSet appropriately.
- Declare custom property wrappers using
@propertyWrapper.
- Understand
wrappedValue and projectedValue.
- Use property wrappers for reusable property behavior.
- Recognize common SwiftUI property wrappers.
- Practice by combining these features in a small iOS project.
27. Conclusion
Extensions, computed properties, and property wrappers are powerful features in Swift that improve code organization, reusability, and property management. Extensions allow developers to add functionality to existing types, computed properties derive values from existing data, and property wrappers encapsulate reusable logic for property access and updates.
Understanding when to use each feature helps developers write cleaner Swift code and build more maintainable iOS applications. Practice these concepts with model types, utility extensions, validation wrappers, and SwiftUI views to strengthen your iOS development skills.
Continue Learning: Explore the JustAcademy iOS App Development Training Course, register for a course demo, or visit the JustAcademy official website.