0
0
Swiftprogramming~15 mins

Custom validation property wrappers in Swift - Deep Dive

Choose your learning style9 modes available
Overview - Custom Validation Property Wrappers
What is it?
Custom Validation Property Wrappers in Swift let you add rules to check if a value is valid whenever it changes. They wrap a property and automatically run validation code, so you don't have to write checks everywhere. This helps keep your code clean and safe by catching errors early. You create your own wrappers to define exactly how to check your data.
Why it matters
Without validation wrappers, you must manually check values all over your code, which is easy to forget or do inconsistently. This can cause bugs or crashes when invalid data sneaks in. Custom validation wrappers solve this by centralizing and automating checks, making your app more reliable and easier to maintain. They save time and prevent frustrating errors.
Where it fits
Before learning this, you should understand Swift properties and basic property wrappers. After this, you can explore Swift's built-in validation features and how to combine wrappers with SwiftUI forms for user input validation.
Mental Model
Core Idea
A custom validation property wrapper is like a gatekeeper that automatically checks and approves values before they are stored in a property.
Think of it like...
Imagine a security guard at a building entrance who checks each visitor's ID before letting them in. The property wrapper is the guard, and the property is the building. Only valid visitors (values) get inside.
┌─────────────────────────────┐
│ Property Wrapper (Gatekeeper)│
│ ┌─────────────────────────┐ │
│ │ Validation Logic Runs   │ │
│ │ on New Value Assignment │ │
│ └────────────┬────────────┘ │
│              │              │
│      Valid? ──┼─ Yes ──────▶│
│              │              │
│             No              │
│              ▼              │
│    Reject or Correct Value  │
└─────────────────────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding Property Wrappers Basics
🤔
Concept: Learn what property wrappers are and how they modify property behavior in Swift.
A property wrapper is a special structure that wraps a property to add extra behavior. You define a wrapper with @propertyWrapper and use it by prefixing a property with @YourWrapperName. It controls how the property stores and retrieves its value.
Result
You can create a simple wrapper that logs every time a property changes.
Understanding property wrappers is essential because validation wrappers build on this mechanism to add automatic checks.
2
FoundationBasic Validation Logic in Swift
🤔
Concept: Learn how to write simple validation functions to check if values meet rules.
Validation means checking if a value is acceptable. For example, a function can check if a string is not empty or if a number is within a range. These functions return true or false based on the check.
Result
You can write a function like `func isValid(_ value: String) -> Bool` that returns true if the string is not empty.
Knowing how to write validation logic is the foundation for creating wrappers that automate these checks.
3
IntermediateCreating a Simple Validation Wrapper
🤔Before reading on: do you think a wrapper can reject invalid values by preventing assignment, or just warn about them? Commit to your answer.
Concept: Combine property wrappers with validation logic to automatically check values when assigned.
@propertyWrapper struct NonEmpty { private var value: String = "" var wrappedValue: String { get { value } set { if !newValue.isEmpty { value = newValue } } } } Use it as `@NonEmpty var name: String`.
Result
When you try to assign an empty string, the property keeps its old value instead.
Knowing that wrappers can control assignment lets you enforce rules automatically and avoid invalid states.
4
IntermediateAdding Custom Error Handling
🤔Before reading on: do you think property wrappers can throw errors directly during assignment? Commit to your answer.
Concept: Enhance wrappers to report validation failures with errors or messages.
Since property wrappers can't throw errors in setters, you can add an extra property to hold validation status or error messages. For example, add a `var isValid: Bool` or `var errorMessage: String?` that updates when validation fails. This way, users of the property can check if the value is valid.
Result
You get feedback about validation without crashing the program.
Understanding how to communicate validation results helps build user-friendly and safe APIs.
5
IntermediateMaking Validation Generic and Reusable
🤔Before reading on: do you think a single wrapper can validate different types of data, or do you need one per type? Commit to your answer.
Concept: Use generics and closures to create flexible validation wrappers for any data type.
@propertyWrapper struct Validate { private var value: Value private let validator: (Value) -> Bool var wrappedValue: Value { get { value } set { if validator(newValue) { value = newValue } } } init(wrappedValue: Value, validator: @escaping (Value) -> Bool) { self.value = wrappedValue self.validator = validator } } Use it like `@Validate(validator: { $0 > 0 }) var positiveNumber: Int`.
Result
You can reuse one wrapper for many validation rules and types.
Generic wrappers reduce code duplication and increase flexibility in validation.
6
AdvancedCombining Multiple Validation Wrappers
🤔Before reading on: do you think stacking multiple wrappers applies all validations in order, or only the first one? Commit to your answer.
Concept: Stack property wrappers to apply several validation rules on one property.
Swift allows stacking wrappers by applying multiple @Wrappers above a property. Each wrapper runs its logic in order. For example: @NonEmpty @Validate(validator: { $0.count <= 10 }) var username: String This means username must be non-empty and at most 10 characters. Each wrapper controls assignment and validation.
Result
You get combined validation rules applied automatically.
Knowing how wrappers compose helps build complex validation logic cleanly.
7
ExpertAdvanced: Using Projected Values for Validation Feedback
🤔Before reading on: do you think property wrappers can expose extra info beyond the property value itself? Commit to your answer.
Concept: Use the projectedValue feature to provide validation status or error messages alongside the property value.
@propertyWrapper struct ValidatedString { private var value: String = "" private(set) var isValid = false var wrappedValue: String { get { value } set { isValid = !newValue.isEmpty if isValid { value = newValue } } } var projectedValue: Bool { isValid } } Use it as: @ValidatedString var name: String if !$name { print("Invalid name") }
Result
You can check validation status easily without extra code.
Understanding projected values unlocks powerful ways to communicate validation results cleanly.
Under the Hood
Swift property wrappers are structs or classes with a wrappedValue property. When you assign to the wrapped property, Swift calls the setter of wrappedValue inside the wrapper. This lets the wrapper run code before storing the value. The compiler rewrites property access to use the wrapper's getter and setter. Projected values are extra properties exposed by the wrapper for additional info.
Why designed this way?
Property wrappers were designed to add reusable behavior to properties without changing their syntax. This keeps code clean and readable. Validation wrappers leverage this to centralize checks. The design avoids throwing errors in setters to keep property access simple and safe, so feedback is given via projected values or separate properties.
┌───────────────────────────────┐
│ Swift Property Wrapper Struct  │
│ ┌───────────────────────────┐ │
│ │ var wrappedValue: Type     │ │
│ │ get/set with validation   │ │
│ └─────────────┬─────────────┘ │
│               │               │
│  Compiler rewrites property    │
│  access to call wrapper logic  │
│               │               │
│ ┌─────────────▼─────────────┐ │
│ │ User Code: @Wrapper var x  │ │
│ │ Access x calls wrapper's   │ │
│ │ wrappedValue getter/setter │ │
│ └───────────────────────────┘ │
└───────────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Can property wrappers throw errors directly when setting a value? Commit to yes or no.
Common Belief:Property wrappers can throw errors during property assignment to reject invalid values.
Tap to reveal reality
Reality:Property wrappers cannot throw errors in wrappedValue setters because property accessors in Swift do not support throwing. Instead, they use other ways to signal validation failure.
Why it matters:Expecting errors to be thrown causes confusion and misuse. Without knowing this, developers might try unsupported patterns leading to compiler errors or unsafe code.
Quick: Does stacking multiple property wrappers apply all validations or just the first? Commit to your answer.
Common Belief:Only the first property wrapper in a stack runs its validation; others are ignored.
Tap to reveal reality
Reality:All stacked property wrappers run their validation in order, each wrapping the previous one, so all validations apply.
Why it matters:Misunderstanding stacking leads to incomplete validation and bugs where some rules are silently skipped.
Quick: Does a property wrapper always reject invalid values by preventing assignment? Commit to yes or no.
Common Belief:Validation wrappers always prevent invalid values from being assigned to the property.
Tap to reveal reality
Reality:Some wrappers may reject invalid values by ignoring assignments, but others may accept all values and only mark them invalid via projected values or flags.
Why it matters:Assuming all wrappers block invalid data can cause unexpected behavior if a wrapper only signals errors without blocking.
Quick: Can you use property wrappers to validate properties in classes and structs equally? Commit to your answer.
Common Belief:Property wrappers work the same way for both classes and structs without any difference.
Tap to reveal reality
Reality:While wrappers work on both, value semantics of structs mean validation behaves differently on copies, which can cause subtle bugs if not understood.
Why it matters:Ignoring value vs reference semantics can lead to validation state being lost or inconsistent in structs.
Expert Zone
1
Validation wrappers often combine with SwiftUI's @State or @Published to trigger UI updates on validation changes, a subtle but powerful pattern.
2
Using projectedValue to expose detailed validation errors enables complex form validation flows without cluttering business logic.
3
Stacking wrappers can cause performance overhead or unexpected behavior if wrappers modify values in conflicting ways; understanding wrapper composition order is critical.
When NOT to use
Avoid custom validation wrappers when validation requires asynchronous checks (like server validation) or complex multi-property dependencies. Instead, use dedicated validation frameworks or combine with Combine publishers for reactive validation.
Production Patterns
In production, validation wrappers are used to enforce input rules in models and view models, often combined with UI frameworks to disable buttons or show error messages automatically. They help keep validation logic close to data and reduce boilerplate.
Connections
Design Patterns - Decorator
Validation wrappers act like decorators by wrapping properties to add behavior without changing their interface.
Understanding decorators in design patterns helps grasp how wrappers add validation transparently.
Functional Programming - Higher-Order Functions
Validation closures passed to generic wrappers are higher-order functions that customize behavior.
Knowing higher-order functions clarifies how validation logic is injected flexibly into wrappers.
Quality Control in Manufacturing
Validation wrappers resemble quality inspectors checking products before they move on.
Seeing validation as quality control helps appreciate its role in preventing defects early.
Common Pitfalls
#1Ignoring validation results and assuming all assigned values are valid.
Wrong approach:@Validate(validator: { $0 > 0 }) var positiveNumber: Int positiveNumber = -5 print(positiveNumber) // Prints -5 even though invalid
Correct approach:@Validate(validator: { $0 > 0 }) var positiveNumber: Int positiveNumber = -5 print(positiveNumber) // Prints previous valid value or default
Root cause:Not handling the wrapper's rejection of invalid values leads to unexpected property states.
#2Trying to throw errors directly in wrappedValue setter.
Wrong approach:@propertyWrapper struct Validate { private var value: Int = 0 var wrappedValue: Int { get { value } set { if newValue < 0 { throw ValidationError() } value = newValue } } }
Correct approach:@propertyWrapper struct Validate { private var value: Int = 0 var wrappedValue: Int { get { value } set { if newValue >= 0 { value = newValue } } } }
Root cause:Swift property setters cannot throw errors; misunderstanding this causes compile errors.
#3Stacking wrappers without understanding order causing unexpected validation.
Wrong approach:@NonEmpty @MaxLength(5) var username: String // MaxLength runs before NonEmpty, allowing empty strings
Correct approach:@MaxLength(5) @NonEmpty var username: String // NonEmpty runs first, blocking empty strings before length check
Root cause:Not knowing wrapper stacking order leads to validation rules applied in wrong sequence.
Key Takeaways
Custom validation property wrappers automate checking values when assigned, keeping code clean and safe.
They work by wrapping properties and running validation logic inside the wrappedValue setter.
Because setters can't throw errors, wrappers use flags or projected values to signal validation results.
Generic wrappers with validation closures make validation reusable and flexible for many types.
Stacking wrappers applies multiple validations in order, but order matters and can affect behavior.