Skip to main content
Breeze uses modern Swift and SwiftUI patterns to create a maintainable, scalable, and thread-safe application.

Core Architectural Patterns

MVVM

Model-View-ViewModel for clear separation of concerns

Actor Pattern

Thread-safe service layer using Swift actors

Declarative UI

SwiftUI for reactive, declarative interfaces

Async/Await

Modern concurrency for clean async code

MVVM Pattern

Breeze implements the Model-View-ViewModel pattern for clear separation between UI and business logic.

Architecture Flow

ViewModel Example

The DashboardViewModel demonstrates the MVVM pattern:
import Foundation
import SwiftUI
import CoreLocation

/// Main view model for the dashboard
@MainActor
class DashboardViewModel: NSObject, ObservableObject {
    // MARK: - Published Properties
    @Published var airQuality: AirQuality?
    @Published var pollutants: [PollutantReading] = []
    @Published var pollenItems: [PollenItem] = []
    @Published var climateData: [ClimateDataPoint] = []
    @Published var locationName: String = ""
    @Published var isLoading = false
    @Published var errorMessage: String?
    @Published var searchQuery = ""
    @Published var searchResults: [City] = []
    @Published var useFahrenheit = true
    
    // MARK: - Private Properties
    private let locationManager = CLLocationManager()
    private var searchTask: Task<Void, Never>?
    
    // MARK: - Computed Properties
    var aqiStatus: AQIStatus? {
        guard let aqi = airQuality?.usAQI else { return nil }
        return AQIStatus.from(aqi: aqi)
    }
    
    var aqiColor: Color {
        guard let status = aqiStatus else { return .gray }
        switch status.color {
        case "aqiGood": return .aqiGood
        case "aqiModerate": return .aqiModerate
        case "aqiUnhealthy": return .aqiUnhealthy
        default: return .gray
        }
    }
    
    // MARK: - Data Fetching
    func fetchAllData(latitude: Double, longitude: Double) async {
        isLoading = true
        errorMessage = nil
        
        // Fetch air quality
        do {
            let aq = try await AirQualityService.shared.fetchAirQuality(
                latitude: latitude,
                longitude: longitude
            )
            self.airQuality = aq
            
            // Create pollutant readings
            self.pollutants = [
                PollutantReading(type: .pm25, value: aq.pm25),
                PollutantReading(type: .pm10, value: aq.pm10),
                PollutantReading(type: .no2, value: aq.nitrogenDioxide)
            ]
        } catch {
            self.errorMessage = "Unable to fetch air quality data."
        }
        
        isLoading = false
    }
}
Key MVVM Characteristics:
  • @Published properties for reactive state updates
  • @MainActor ensures UI updates on main thread
  • Business logic separated from UI
  • Computed properties for derived state

View Observing ViewModel

import SwiftUI

struct DashboardView: View {
    @ObservedObject var viewModel: DashboardViewModel
    
    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                // Location Header
                Text(viewModel.locationName)
                    .font(.title)
                    .fontWeight(.semibold)
                
                // AQI Card
                AQICard(viewModel: viewModel)
                
                // Pollutants Grid
                PollutantsGrid(pollutants: viewModel.pollutants)
            }
        }
        .refreshable {
            await viewModel.fetchAllData(latitude: 0, longitude: 0)
        }
    }
}
Views observe the ViewModel using @ObservedObject. When published properties change, SwiftUI automatically re-renders affected views.

Actor Pattern for Services

All services use Swift’s actor pattern for thread-safe API communication.

Why Actors?

Actors ensure only one task can access mutable state at a time, preventing data races.
Swift automatically manages synchronization - no manual locks or dispatch queues needed.
The compiler enforces safe concurrent access patterns at compile time.

Service Actor Example

import Foundation

/// Service for fetching air quality data from Open-Meteo API
actor AirQualityService {
    static let shared = AirQualityService()
    
    private let baseURL = "https://air-quality-api.open-meteo.com/v1/air-quality"
    
    /// Fetch current air quality for a location
    func fetchAirQuality(latitude: Double, longitude: Double) async throws -> AirQuality {
        var components = URLComponents(string: baseURL)!
        components.queryItems = [
            URLQueryItem(name: "latitude", value: String(latitude)),
            URLQueryItem(name: "longitude", value: String(longitude)),
            URLQueryItem(name: "current", value: "us_aqi,pm10,pm2_5,carbon_monoxide,nitrogen_dioxide,sulphur_dioxide,ozone"),
            URLQueryItem(name: "timezone", value: "auto")
        ]
        
        guard let url = components.url else {
            throw URLError(.badURL)
        }
        
        let (data, response) = try await URLSession.shared.data(from: url)
        
        guard let httpResponse = response as? HTTPURLResponse,
              httpResponse.statusCode == 200 else {
            throw URLError(.badServerResponse)
        }
        
        let decoder = JSONDecoder()
        let result = try decoder.decode(AirQualityResponse.self, from: data)
        
        guard let airQuality = result.current else {
            throw NSError(domain: "AirQualityService", code: 1, 
                         userInfo: [NSLocalizedDescriptionKey: "No air quality data available"])
        }
        
        return airQuality
    }
}
Actor Benefits:
  • actor keyword makes the entire class thread-safe
  • Methods are implicitly async when called from outside
  • Shared singleton pattern (static let shared) is safe
  • No manual synchronization needed

Calling Actor Methods

Actor methods require await when called from outside:
Calling Actors
// From ViewModel (already @MainActor)
let aq = try await AirQualityService.shared.fetchAirQuality(
    latitude: latitude,
    longitude: longitude
)
Actor methods can only be called with await because the actor may need to synchronize access. This prevents blocking the main thread.

Async/Await Pattern

Breeze uses Swift’s modern concurrency with async/await for clean asynchronous code.

Parallel vs Sequential Fetching

// Non-blocking parallel tasks
Task {
    do {
        let pollen = try await PollenService.shared.fetchPollen(
            latitude: latitude,
            longitude: longitude
        )
        self.pollenItems = pollen
    } catch {
        print("Pollen error: \(error)")
    }
}

Task {
    do {
        let climate = try await ClimateService.shared.fetchClimateData(
            latitude: latitude,
            longitude: longitude
        )
        self.climateData = climate
    } catch {
        print("Climate error: \(error)")
    }
}
Pattern Choice:
  • Use sequential (await) when data depends on previous results
  • Use parallel (Task {}) for independent data fetching
  • Balance between performance and data dependencies

SwiftUI Declarative Patterns

Breeze embraces SwiftUI’s declarative approach for building UIs.

Property Wrappers

// ViewModel
@Published var airQuality: AirQuality?        // Observable state
@Published var isLoading = false              // Loading state

// View
@ObservedObject var viewModel: DashboardViewModel  // Observe ViewModel
@State private var showSettings = false            // Local view state
@AppStorage("useFahrenheit") var useFahrenheit = true  // Persisted preference
Property WrapperPurposeScope
@PublishedObservable property in ViewModelViewModel
@ObservedObjectObserve external objectView
@StateLocal view stateSingle view
@AppStorageUserDefaults backed stateApp-wide
@MainActorEnsure main thread executionClass/function

Conditional Rendering

var body: some View {
    VStack {
        if viewModel.isLoading {
            LoadingView()
        } else if let error = viewModel.errorMessage {
            ErrorView(message: error)
        } else if let airQuality = viewModel.airQuality {
            AQICard(airQuality: airQuality)
        } else {
            EmptyStateView()
        }
    }
}

View Composition

Breeze breaks down complex views into smaller components:
View Composition
struct DashboardView: View {
    var body: some View {
        ScrollView {
            VStack(spacing: 20) {
                LocationHeader(name: viewModel.locationName)
                AQICard(viewModel: viewModel)
                PollenView(items: viewModel.pollenItems)
                PollutantsGrid(pollutants: viewModel.pollutants)
                ClimateChartView(data: viewModel.climateData)
            }
        }
    }
}
Each component is self-contained and reusable, making the code easier to test and maintain.

Model Pattern

Models are simple, immutable data structures conforming to Codable.
import Foundation

/// Air quality data from Open-Meteo API
struct AirQuality: Codable {
    let usAQI: Int
    let pm25: Double
    let pm10: Double
    let carbonMonoxide: Double
    let nitrogenDioxide: Double
    let sulphurDioxide: Double
    let ozone: Double
    
    enum CodingKeys: String, CodingKey {
        case usAQI = "us_aqi"
        case pm25 = "pm2_5"
        case pm10
        case carbonMonoxide = "carbon_monoxide"
        case nitrogenDioxide = "nitrogen_dioxide"
        case sulphurDioxide = "sulphur_dioxide"
        case ozone
    }
}
  • Value semantics (copying instead of referencing)
  • Thread-safe by default
  • No reference counting overhead
  • Perfect for immutable data
  • Automatic JSON encoding/decoding
  • Type-safe API responses
  • Custom key mapping with CodingKeys

Pattern Summary

MVVM

  • ViewModels manage state
  • Views observe and render
  • Models represent data
  • Clear separation of concerns

Actor Pattern

  • Thread-safe services
  • Compiler-enforced safety
  • No manual synchronization
  • Singleton pattern with static let

Async/Await

  • Clean asynchronous code
  • Parallel and sequential fetching
  • Error handling with try/catch
  • Task-based concurrency

SwiftUI

  • Declarative UI syntax
  • Reactive data binding
  • View composition
  • State-driven rendering

Best Practices

1

Keep ViewModels focused

One ViewModel per major screen. Extract shared logic into separate services or utilities.
2

Use actors for shared state

Any service that manages shared mutable state should be an actor for thread safety.
3

Prefer async/await over callbacks

Modern concurrency is clearer and less error-prone than completion handlers.
4

Keep views small

Break down complex views into smaller, reusable components.
5

Use @MainActor for ViewModels

Ensures all UI updates happen on the main thread automatically.

Next Steps

Project Structure

Understand the file organization

Services Reference

Explore service implementations

Build docs developers (and LLMs) love