Bird
Raised Fist0
Swiftprogramming~15 mins

Custom validation property wrappers in Swift - Deep Dive

Choose your learning style10 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.

Practice

(1/5)
1. What is the main purpose of a custom validation property wrapper in Swift?
easy
A. To automatically check and enforce rules on property values
B. To change the type of a property at runtime
C. To make properties private and inaccessible
D. To store multiple values in a single property

Solution

  1. Step 1: Understand property wrappers

    Property wrappers in Swift add extra behavior to properties, such as validation.
  2. Step 2: Identify validation role

    Custom validation wrappers check if values meet certain rules before storing them.
  3. Final Answer:

    To automatically check and enforce rules on property values -> Option A
  4. Quick Check:

    Validation = enforce rules [OK]
Hint: Validation wrappers enforce rules on data automatically [OK]
Common Mistakes:
  • Thinking wrappers change property type
  • Confusing validation with access control
  • Assuming wrappers store multiple values
2. Which of the following is the correct syntax to declare a custom validation property wrapper named NonEmpty for a String property in Swift?
easy
A. var name: @NonEmpty String
B. var @NonEmpty name: String
C. @NonEmpty var name: String
D. NonEmpty var name: String

Solution

  1. Step 1: Recall property wrapper syntax

    In Swift, property wrappers are applied with @ before the wrapper name, placed before the variable declaration.
  2. Step 2: Match correct syntax

    Correct syntax is @WrapperName var propertyName: Type, so @NonEmpty var name: String is correct.
  3. Final Answer:

    @NonEmpty var name: String -> Option C
  4. Quick Check:

    @WrapperName before var = correct syntax [OK]
Hint: Use @ before wrapper name, then var property [OK]
Common Mistakes:
  • Placing @ after var
  • Putting wrapper inside type
  • Omitting @ symbol
3. Consider this Swift code using a custom validation property wrapper Positive that only allows positive integers:
 @propertyWrapper
 struct Positive {
     private var value: Int = 1
     var wrappedValue: Int {
         get { value }
         set { value = newValue > 0 ? newValue : 1 }
     }
 }

 struct Account {
     @Positive var balance: Int
 }

 let acc = Account()
 print(acc.balance)
What will be printed when this code runs?
medium
A. 1
B. 0
C. nil
D. Compilation error

Solution

  1. Step 1: Understand default value in wrapper

    The wrapper's private value starts at 1, and sets new values only if positive; else resets to 1.
  2. Step 2: Check initial property value

    Account's balance uses @Positive but no initial value is set, so it uses wrapper's default 1.
  3. Final Answer:

    1 -> Option A
  4. Quick Check:

    Default wrapper value = 1 [OK]
Hint: Default wrapper value used if no initial value given [OK]
Common Mistakes:
  • Expecting 0 as default
  • Assuming nil is possible
  • Thinking code won't compile
4. This Swift code attempts to create a custom validation property wrapper NonNegative but has an error:
 @propertyWrapper
 struct NonNegative {
     var value: Int
     var wrappedValue: Int {
         get { value }
         set { if newValue >= 0 { value = newValue } }
     }
 }

 struct Score {
     @NonNegative var points: Int = -5
 }
What is the main problem causing this code to fail?
medium
A. wrappedValue setter does not handle negative values correctly
B. Property wrapper lacks an initializer to set initial value
C. Cannot assign default value to property with wrapper
D. Missing @ symbol before property wrapper

Solution

  1. Step 1: Check property wrapper requirements

    Property wrappers must have an init(wrappedValue:) to accept initial values.
  2. Step 2: Identify missing initializer

    NonNegative lacks init(wrappedValue:) so compiler cannot assign -5 to points.
  3. Final Answer:

    Property wrapper lacks an initializer to set initial value -> Option B
  4. Quick Check:

    Missing init(wrappedValue:) = error [OK]
Hint: Add init(wrappedValue:) to accept initial values [OK]
Common Mistakes:
  • Ignoring missing initializer
  • Blaming setter logic
  • Thinking default value assignment is invalid
5. You want to create a custom validation property wrapper LimitedLength that limits a String property's length to a maximum number of characters. Which of the following implementations correctly enforces this limit and allows setting the max length when declaring the property?
 @propertyWrapper
 struct LimitedLength {
     private var value: String = ""
     private let maxLength: Int

     var wrappedValue: String {
         get { value }
         set {
             if newValue.count <= maxLength {
                 value = newValue
             } else {
                 value = String(newValue.prefix(maxLength))
             }
         }
     }

     // Which initializer is correct?
     ???
 }

 struct User {
     @LimitedLength(maxLength: 5) var username: String
 }
hard
A. init(maxLength: Int) { self.maxLength = maxLength }
B. init(wrappedValue: String) { self.value = wrappedValue; maxLength = 5 }
C. init() { maxLength = 5 }
D. init(wrappedValue: String, maxLength: Int) { self.maxLength = maxLength; self.value = wrappedValue }

Solution

  1. Step 1: Understand property wrapper initialization with parameters

    When a wrapper has parameters and initial value, it needs init(wrappedValue:maxLength:) to accept both.
  2. Step 2: Match initializer to usage

    Usage passes maxLength: 5 and no initial value, so init(wrappedValue:maxLength:) fits best to set both.
  3. Final Answer:

    init(wrappedValue: String, maxLength: Int) { self.maxLength = maxLength; self.value = wrappedValue } -> Option D
  4. Quick Check:

    Use init(wrappedValue:maxLength:) for parameters [OK]
Hint: Use init(wrappedValue:maxLength:) to accept value and limit [OK]
Common Mistakes:
  • Using init(maxLength:) only misses initial value
  • Using init() without parameters
  • Using init(wrappedValue:) without maxLength
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard