solid-swift

fusengine's avatarfrom fusengine

SOLID principles for Swift 6 and SwiftUI (iOS 26+). Apple recommended patterns, @Observable, actors, Preview-driven development.

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 10, 2026

When & Why to Use This Skill

This Claude skill acts as an expert architect and senior developer for modern iOS development, specifically optimized for Swift 6 and SwiftUI (iOS 26+). It enforces strict SOLID principles, Apple-recommended design patterns, and modern concurrency models like actors and @Observable. By integrating real-time documentation research and build validation, it ensures the delivery of high-quality, maintainable, and future-proof Apple platform codebases.

Use Cases

  • Architecting scalable SwiftUI applications using a modular structure with clear separation between Protocols, Services, ViewModels, and Views.
  • Refactoring legacy Swift codebases to adopt Swift 6 concurrency standards, including actors, @MainActor, and Sendable types.
  • Implementing 'Preview-Driven Development' to ensure every UI component is fully testable and visually verified within Xcode Previews.
  • Enforcing strict clean code standards such as limiting file lengths, mandatory Swift documentation, and localized string catalogs for global readiness.
  • Automating the research and validation process using Apple Docs MCP and XcodeBuildMCP to ensure code aligns with the latest 2025-2026 API standards.
namesolid-swift
descriptionSOLID principles for Swift 6 and SwiftUI (iOS 26+). Apple recommended patterns, @Observable, actors, Preview-driven development.

SOLID Swift - Apple Best Practices 2025

Current Date (CRITICAL)

Today: January 2026 - ALWAYS use the current year for your searches. Search with "2025" or "2026", NEVER with past years.

MANDATORY: Research Before Coding

CRITICAL: Check today's date first, then search documentation and web BEFORE writing any code.

Priority Order (2026)

  1. ⭐ Apple Docs MCP (PRIMARY) - Official Apple documentation with WWDC 2014-2025

    • Search SwiftUI, UIKit, Foundation, CoreData, ARKit docs
    • Get framework details and symbol information
    • Search WWDC sessions with transcripts (offline access)
    • Access Apple sample code
    • See: mcp-tools/apple-docs-mcp.md
  2. Context7 (SECONDARY) - For third-party libraries and community packages

    • Use only if Apple Docs MCP doesn't have the answer
    • Good for SPM packages, community libraries
  3. Exa web search (TERTIARY) - Latest trends and blog posts

    • Use with current year for newest patterns
    • Community tutorials and articles

Build Validation (NEW 2026)

  1. ⭐ XcodeBuildMCP (MANDATORY after code changes)
    • Build project to validate changes
    • Inspect build errors autonomously
    • Clean builds when needed
    • See: mcp-tools/xcode-build-mcp.md
WORKFLOW (2026):
1. Check date
2. Apple Docs MCP: Search API/WWDC (current year)
3. If not found → Context7 → Exa web search
4. Apply latest patterns
5. Code implementation
6. XcodeBuildMCP: Build to validate
7. Fix errors if any → Rebuild

Search queries (replace YYYY with current year):

Apple Docs MCP:

  • SwiftUI [component] YYYY
  • [Framework] new APIs
  • WWDC sessions: [topic] WWDC YYYY

Exa web search (if Apple Docs insufficient):

  • Swift [feature] YYYY best practices
  • SwiftUI [component] YYYY tutorial

Never assume - always verify current APIs and patterns exist for the current year.


Codebase Analysis (MANDATORY)

Before ANY implementation:

  1. Explore project structure to understand architecture
  2. Read existing related files to follow established patterns
  3. Identify naming conventions, coding style, and patterns used
  4. Understand data flow and dependencies

Continue implementation by:

  • Following existing patterns and conventions
  • Matching the coding style already in place
  • Respecting the established architecture
  • Integrating with existing services/components

DRY - Reuse Before Creating (MANDATORY)

Before writing ANY new code:

  1. Search existing codebase for similar functionality
  2. Check shared locations: Core/Extensions/, Core/Utilities/, Core/Protocols/
  3. If similar code exists → extend/reuse instead of duplicate

When creating new code:

  • Extract repeated logic (3+ occurrences) into shared helpers
  • Place shared utilities in Core/Utilities/
  • Use Extensions for type enhancements
  • Document reusable functions with ///

Absolute Rules (MANDATORY)

1. Files < 150 lines

  • Split at 120 lines - Never exceed 150
  • Views < 80 lines (extract subviews at 30+)
  • ViewModels < 100 lines
  • Services < 100 lines
  • Models < 50 lines

2. Protocols Separated

Sources/
├── Protocols/           # Protocols ONLY
│   ├── UserServiceProtocol.swift
│   └── AuthProviderProtocol.swift
├── Services/            # Implementations
│   └── UserService.swift
├── ViewModels/          # @Observable classes
│   └── UserViewModel.swift
└── Views/               # SwiftUI Views
    └── UserView.swift

3. Swift Documentation Mandatory

/// Fetches user by ID from remote API.
///
/// - Parameter id: User unique identifier
/// - Returns: User if found, nil otherwise
/// - Throws: `NetworkError` on connection failure
func fetchUser(id: String) async throws -> User?

4. Preview-Driven Development (Apple)

Every View MUST have a #Preview:

#Preview {
    UserProfileView(user: .preview)
}

#Preview("Loading State") {
    UserProfileView(user: nil)
}

Apple Architecture 2025

Sources/
├── App/
│   └── MyApp.swift
├── Features/                # Feature modules
│   ├── Auth/
│   │   ├── Views/
│   │   ├── ViewModels/
│   │   ├── Services/
│   │   └── Protocols/
│   └── Profile/
├── Core/                    # Shared
│   ├── Services/
│   ├── Models/
│   ├── Protocols/
│   └── Extensions/
└── Resources/

@Observable over ObservableObject

// ❌ OLD - ObservableObject
class UserViewModel: ObservableObject {
    @Published var user: User?
}

// ✅ NEW - @Observable (iOS 17+)
@Observable
final class UserViewModel {
    var user: User?
    var isLoading = false

    private let service: UserServiceProtocol

    init(service: UserServiceProtocol) {
        self.service = service
    }
}

SOLID Principles for Swift

S - Single Responsibility

1 type = 1 purpose

// ❌ BAD - View does everything
struct UserView: View {
    @State private var user: User?

    var body: some View {
        // fetching, validation, formatting, rendering...
    }
}

// ✅ GOOD - Separation
struct UserView: View {
    @State private var viewModel: UserViewModel

    var body: some View {
        UserContent(user: viewModel.user)
    }
}

O - Open/Closed

Protocols for extensibility

// Protocols/AuthProviderProtocol.swift
protocol AuthProviderProtocol: Sendable {
    func signIn(credentials: Credentials) async throws -> Session
    func signOut() async
}

// Services/AppleAuthProvider.swift
final class AppleAuthProvider: AuthProviderProtocol { }

// Services/GoogleAuthProvider.swift
final class GoogleAuthProvider: AuthProviderProtocol { }

L - Liskov Substitution

All implementations respect contracts

// Any provider works
let auth: AuthProviderProtocol = AppleAuthProvider()
// or
let auth: AuthProviderProtocol = GoogleAuthProvider()

// Both work identically
try await auth.signIn(credentials: creds)

I - Interface Segregation

Small, focused protocols

// ❌ BAD - Too broad
protocol UserProtocol {
    func fetch() async
    func update() async
    func delete() async
    func sendNotification() async
}

// ✅ GOOD - Separated
protocol Fetchable { func fetch() async }
protocol Updatable { func update() async }
protocol Deletable { func delete() async }

D - Dependency Inversion

Depend on protocols, inject implementations

// ViewModel depends on protocol
@Observable
final class UserViewModel {
    private let service: UserServiceProtocol

    init(service: UserServiceProtocol) {
        self.service = service
    }
}

// Injection at app level
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(UserViewModel(service: UserService()))
        }
    }
}

Swift 6 Concurrency

Actor for Shared State

actor UserCache {
    private var users: [String: User] = [:]

    func get(_ id: String) -> User? {
        users[id]
    }

    func set(_ user: User) {
        users[user.id] = user
    }
}

@MainActor for UI

@MainActor
@Observable
final class UserViewModel {
    var user: User?
    var isLoading = false

    func load() async {
        isLoading = true
        defer { isLoading = false }

        user = try? await service.fetchUser()
    }
}

Sendable Types

// Models must be Sendable for concurrency
struct User: Codable, Sendable {
    let id: String
    let name: String
    let email: String
}

Structured Concurrency

// ✅ GOOD - Task groups
func loadDashboard() async throws -> Dashboard {
    async let user = fetchUser()
    async let stats = fetchStats()
    async let notifications = fetchNotifications()

    return Dashboard(
        user: try await user,
        stats: try await stats,
        notifications: try await notifications
    )
}

SwiftUI Templates

View (< 80 lines)

import SwiftUI

/// User profile view displaying user information.
struct UserProfileView: View {
    let user: User?

    var body: some View {
        Group {
            if let user {
                UserContent(user: user)
            } else {
                ProgressView()
            }
        }
        .navigationTitle("profile.title")
    }
}

// MARK: - Subviews

private struct UserContent: View {
    let user: User

    var body: some View {
        VStack {
            Text(user.name)
            Text(user.email)
        }
    }
}

// MARK: - Preview

#Preview {
    UserProfileView(user: .preview)
}

ViewModel (< 100 lines)

import Foundation

/// ViewModel for user profile screen.
@MainActor
@Observable
final class UserProfileViewModel {
    // MARK: - State

    var user: User?
    var isLoading = false
    var error: Error?

    // MARK: - Dependencies

    private let service: UserServiceProtocol

    // MARK: - Init

    init(service: UserServiceProtocol) {
        self.service = service
    }

    // MARK: - Actions

    /// Loads user profile from API.
    func load() async {
        isLoading = true
        defer { isLoading = false }

        do {
            user = try await service.fetchCurrentUser()
        } catch {
            self.error = error
        }
    }
}

Protocol

import Foundation

/// Protocol for user-related operations.
protocol UserServiceProtocol: Sendable {
    /// Fetches the current authenticated user.
    func fetchCurrentUser() async throws -> User

    /// Updates user profile.
    func updateUser(_ user: User) async throws -> User
}

Service

import Foundation

/// Implementation of UserServiceProtocol using URLSession.
final class UserService: UserServiceProtocol {
    private let session: URLSession

    init(session: URLSession = .shared) {
        self.session = session
    }

    func fetchCurrentUser() async throws -> User {
        let url = URL(string: "https://api.example.com/me")!
        let (data, _) = try await session.data(from: url)
        return try JSONDecoder().decode(User.self, from: data)
    }

    func updateUser(_ user: User) async throws -> User {
        // Implementation...
        return user
    }
}

Localization (MANDATORY)

All user-facing text MUST use String Catalogs:

// ✅ GOOD - Localized
Text("profile.welcome.title")
Button("button.save") { }

// With interpolation
Text("profile.greeting \(user.name)")

// ❌ BAD - Hardcoded
Text("Welcome!")
Button("Save") { }

Key naming: module.screen.element


iOS 26 / WWDC 2025

Liquid Glass Design

.glassBackgroundEffect()
.liquidGlass()

SwiftUI Performance

  • 6x faster list loading
  • 16x faster updates on macOS
  • Use SwiftUI Performance Instrument

3D Layouts (visionOS)

SpatialLayout {
    Model3D(named: "object")
}

Response Guidelines

  1. Research first - MANDATORY: Search Context7 + Exa before ANY code
  2. Show complete code - Working examples, not snippets
  3. Explain decisions - Why this pattern over alternatives
  4. Include previews - Always add #Preview for views
  5. Handle errors - Never ignore, always handle gracefully
  6. Consider accessibility - VoiceOver, Dynamic Type
  7. Document code - /// for public APIs

Forbidden

  • ❌ Coding without researching docs first (ALWAYS research)
  • ❌ Using outdated APIs without checking current year docs
  • ❌ Files > 150 lines
  • ❌ Protocols in implementation files
  • ❌ ObservableObject (use @Observable)
  • ❌ Completion handlers (use async/await)
  • ❌ Missing #Preview
  • ❌ Hardcoded strings (use String Catalogs)
  • ❌ Force unwrap without validation
  • ❌ Missing /// documentation
  • ❌ Views > 80 lines without extraction
  • ❌ Non-Sendable types in async contexts