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.
Automatic Synchronization
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:
// 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
Parallel Fetching
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 Wrapper Purpose Scope @PublishedObservable property in ViewModel ViewModel @ObservedObjectObserve external object View @StateLocal view state Single view @AppStorageUserDefaults backed state App-wide @MainActorEnsure main thread execution Class/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:
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
Keep ViewModels focused
One ViewModel per major screen. Extract shared logic into separate services or utilities.
Use actors for shared state
Any service that manages shared mutable state should be an actor for thread safety.
Prefer async/await over callbacks
Modern concurrency is clearer and less error-prone than completion handlers.
Keep views small
Break down complex views into smaller, reusable components.
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