Skip to content

Instantly share code, notes, and snippets.

@brennanMKE
Last active July 28, 2025 21:19
Show Gist options
  • Save brennanMKE/8b276e2e6e042496fc68a3ed5786663c to your computer and use it in GitHub Desktop.
Save brennanMKE/8b276e2e6e042496fc68a3ed5786663c to your computer and use it in GitHub Desktop.
Swift Exercises

Swift Concurrency Exercises

🟒 Beginner (1–5): Core Concepts & Basics

  1. Awaiting Simple Async Function

    • Write an async function that returns a greeting string.
    • Test that it returns the expected value using #expect.
  2. Async Throwing Function

    • Create an async throws function that parses an integer from a string.
    • Test both successful and throwing behavior.
  3. Async Let Parallelism

    • Run two async sleep operations in parallel using async let.
    • Test that the elapsed time confirms parallelism.
  4. Simple Actor for Counter

    • Create an actor that safely increments a counter.
    • Test concurrent increments and final value.
  5. Async Sequence Using AsyncStream

    • Create an AsyncStream that emits numbers 1 to 5.
    • Consume it and test that all values are received in order.

🟑 Intermediate (6–14): Structured Concurrency, Cancellation, Isolation

  1. Task Group Summation

    • Use withTaskGroup to sum an array of integers in parallel.
    • Test with a known set and assert correct result.
  2. Cooperative Cancellation

    • Use Task.checkCancellation() inside a loop.
    • Test cancellation by cancelling the task early and ensuring partial results or failure.
  3. MainActor vs nonisolated

    • Create a type isolated to MainActor with a nonisolated logging method.
    • Test thread correctness using Task.detached.
  4. Actor with Private Mutable State

    • Build a BankAccount actor with deposit and balance-check methods.
    • Test that simultaneous deposits produce the correct total.
  5. Bridging Delegate to AsyncStream

    • Bridge a fake delegate-style API to an AsyncStream.
    • Test by simulating callback invocations.
  6. Using Task Detachment

    • Launch a detached task to do background processing and return a result.
    • Test with a simulated background operation.
  7. Bindables with Observable Macro

    • Define a @Observable model and bind a property with @Bindable.
    • Test changes propagate via a mock view model update.
  8. Nested Task Groups with Result Aggregation

    • Compose task groups that download batches of data concurrently.
    • Test with mock inputs and aggregate results.
  9. Handling Sendable Violations

    • Define a class that fails Sendable, then fix it using isolation or final.
    • Test compilation with strict concurrency and validate no warnings.

πŸ”΄ Advanced (15–20): Isolation, Error Handling, Async Design Patterns

  1. Unstructured Task Lifetime

    • Start an unstructured task and manage its lifecycle manually.
    • Test that cancellation and value retrieval work as expected.
  2. Timeout Handling with Task

    • Implement a timeout mechanism using Task.sleep.
    • Test both success and timeout behavior.
  3. Async Sequence Transformer

    • Write a utility that takes any AsyncSequence and returns a new one with transformed values.
    • Test using AsyncStream as input and collect the transformed output.
  4. Using withTaskCancellationHandler

    • Simulate a task with clean-up logic triggered upon cancellation.
    • Test the order of operations during cancellation.
  5. Actor Isolation Violation Fix

    • Try to mutate an actor's property from outside and fix the violation by refactoring with isolated methods.
    • Confirm through compiler and test validation.
  6. Testable Concurrency Pipeline

    • Build a pipeline with async stages (e.g., fetch, transform, cache).
    • Use actors or TaskGroup as needed.
    • Write end-to-end tests validating correctness under concurrency and cancellation.

SwiftUI Exercises

SwiftUI + Observations Exercise Set (20 Exercises)

This series strengthens your skills in modern SwiftUI using @Observable, @Bindable, onChange, SE-0475 Observations, and UIKit bridging. Each exercise indicates whether you're using built-in SwiftUI APIs or expected to create custom APIs.


🟒 Beginner (1–6): State, Bindings, and View Decomposition

  1. Use @State and a Button Toggle a Boolean flag that shows or hides a colored rectangle. Animate the transition using .animation().

  2. Use @Binding to Share State with Subview Create a subview that receives a Binding<Bool> to control visibility or selection. Confirm parent and child stay in sync.

  3. Migrate a @Published Model to @Observable Replace ObservableObject and @Published with @Observable. Confirm state changes using @State or @Environment in the view.

  4. Use @Bindable in a Subview Pass a @Bindable model to a detail or form view. Edit properties and confirm the changes reflect in the parent.

  5. Use #Preview with Sample Data Set up #Preview with a sample observable model. Render multiple variations to test layout and content.

  6. Use onChange(of:) for Side Effects React to changes in a model property by logging or updating state. Apply .onChange(of:) directly in the view hierarchy.


🟑 Intermediate (7–14): View Composition and Communication

  1. Create a Custom ViewModifier and .cardStyle() Extension Define a ViewModifier that applies padding, background, and corner radius. Add a View extension to apply it as .cardStyle().

  2. Create and Use a Custom PreferenceKey Create a PreferenceKey to propagate layout info (like size or position) from a child view to a parent. Use .background() and .onPreferenceChange().

  3. Define a Custom EnvironmentKey Define a custom key and value, then inject it using .environment(_:_:). Read it with @Environment.

  4. Use @Environment(SomeType.self) with @Observable Replace @EnvironmentObject with the modern @Environment(SomeType.self) approach. Share state between views cleanly.

  5. Use @ObservationIgnored for Non-Observed Properties Mark a computed or reference property with @ObservationIgnored to avoid triggering re-renders when changed.

  6. Use a @State Array of @Observable Items Track a dynamic list (e.g., notes or to-dos). Use @Bindable to update individual items in subviews.

  7. Implement a View-Local @Observable ViewModel Define a lightweight view-specific model using @State and @Observable. Use it for view-local derived logic.

  8. Create a Shared Parent Model Managing Child Observables Build a parent @Observable that owns child models. Update one (e.g. selection) and verify propagation through bindings.


πŸ”΄ Advanced (15–20): Observations, UIKit Bridging, and Coordination

  1. Demonstrate SE-0475 Observations In body, read one property and mutate another. Confirm SwiftUI doesn’t re-render unless a read property changes.

  2. Debounce Observed Input Changes Watch changes to an observed @Bindable property and implement a debounced side effect using Task.sleep.

  3. Create a Modal Sheet with @Bindable Editing Use .sheet(isPresented:) to show a view that edits a @Bindable model. Reflect changes back in the parent view when dismissed.

  4. UIKit View That Responds to Observed Property Use UIViewControllerRepresentable to embed a UIKit view that updates itself when an observed SwiftUI property changes.

  5. UIKit View That Sets Observed Property Embed a UIKit slider or text field that updates a SwiftUI @Observable property using a Binding or callback closure.

  6. Two-Way SwiftUI–UIKit Observation Bridge Combine the previous two:

    • SwiftUI drives UIKit updates via observation
    • UIKit modifies SwiftUI state through Binding Use this pattern to bridge a fully interactive component.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment