💻 Développement

dev-ios-swift-advisor

Développement iOS natif avec Swift, SwiftUI et UIKit.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-ios-swift-advisor --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-ios-swift-advisor -Launch"

🚀 Déjà installé ?

claude "/dev-ios-swift-advisor"

Ou tapez /dev-ios-swift-advisor dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

iOSSwiftSwiftUIUIKitXcodeiPhone appAppleCore DataCombineApp Store

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-ios-swift-advisor ~/.claude/skills/

Payload du plugin : skills/dev-ios-swift-advisor · source éditable : dev-skills/ios-swift-advisor

📖 Manuel

iOS Swift Advisor

Workflow

1. Choix d'architecture

ContexteArchitecture recommandée
Petit projet / MVP SwiftUIMVVM + @Observable
SwiftUI complexe, testabilité maximaleTCA (The Composable Architecture)
Grande équipe, UIKit dominantVIPER ou Clean Swift (VIP)
Migration progressive UIKit→SwiftUIMVVM + Coordinator
// MVVM avec @Observable (Swift 5.9+, iOS 17)
@Observable
final class ProfileViewModel {
    var username: String = ""
    var isLoading: Bool = false
    private let repository: ProfileRepository

    init(repository: ProfileRepository) {
        self.repository = repository
    }

    func fetchProfile(id: String) async {
        isLoading = true
        defer { isLoading = false }
        username = await repository.fetch(id: id).name
    }
}

// Dans la View — pas besoin de @ObservedObject avec @Observable
struct ProfileView: View {
    @State private var vm = ProfileViewModel(repository: .live)
    var body: some View {
        Group {
            if vm.isLoading { ProgressView() }
            else { Text(vm.username) }
        }
        .task { await vm.fetchProfile(id: "42") }
    }
}

2. UI : SwiftUI vs UIKit

Critères de décision :

// Interop UIKit → SwiftUI
struct WebViewRepresentable: UIViewRepresentable {
    let url: URL
    func makeUIView(context: Context) -> WKWebView { WKWebView() }
    func updateUIView(_ uiView: WKWebView, context: Context) {
        uiView.load(URLRequest(url: url))
    }
}

Property wrappers — règle rapide :

3. Concurrence et networking (Swift Concurrency)

// URLSession + async/await + Codable
struct APIClient {
    func fetch<T: Decodable>(_ endpoint: URL) async throws -> T {
        let (data, response) = try await URLSession.shared.data(from: endpoint)
        guard (response as? HTTPURLResponse)?.statusCode == 200 else {
            throw APIError.badStatus
        }
        return try JSONDecoder().decode(T.self, from: data)
    }
}

// Annulation propre avec Task
func loadData() {
    Task {
        do {
            let user: User = try await APIClient().fetch(endpoint)
            await MainActor.run { self.user = user } // màj UI sur le main actor
        } catch is CancellationError { /* ignoré */ }
          catch { handle(error) }
    }
}

Isoler le main actor : annoter les ViewModels avec @MainActor pour éviter les data races sur les propriétés UI.

@MainActor
@Observable
final class FeedViewModel { ... }

4. Persistence — arbre de décision

Données sensibles (tokens, mdp) → Keychain
Préférences légères             → UserDefaults / @AppStorage
Modèle relationnel, migrations  → Core Data (NSPersistentCloudKitContainer pour iCloud)
Nouveau projet iOS 17+          → SwiftData (@Model, .modelContainer)
Sync multi-appareils            → CloudKit ou Firebase Firestore
// SwiftData (iOS 17+)
@Model
final class TodoItem {
    var title: String
    var isDone: Bool
    init(title: String) { self.title = title; self.isDone = false }
}

// Dans App
@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup { ContentView() }
            .modelContainer(for: TodoItem.self)
    }
}

5. Tests

// XCTest async
func testFetchProfile() async throws {
    let sut = ProfileViewModel(repository: MockRepository())
    await sut.fetchProfile(id: "1")
    XCTAssertEqual(sut.username, "Alice")
}

// Protocol pour le mock
protocol ProfileRepository {
    func fetch(_ id: String) async -> Profile
}
struct MockRepository: ProfileRepository {
    func fetch(_ id: String) async -> Profile { Profile(name: "Alice") }
}

6. Sécurité iOS

// Keychain (wrapper simplifié)
func saveToken(_ token: String) {
    let data = Data(token.utf8)
    let query: [CFString: Any] = [
        kSecClass: kSecClassGenericPassword,
        kSecAttrAccount: "authToken",
        kSecValueData: data,
        kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
    ]
    SecItemDelete(query as CFDictionary)
    SecItemAdd(query as CFDictionary, nil)
}

// Biométrie (Face ID / Touch ID)
let context = LAContext()
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
                        localizedReason: "Confirmer votre identité") { ok, _ in
    DispatchQueue.main.async { if ok { openSecureContent() } }
}

7. App Store — checklist soumission

  1. Activer Automatic Signing dans Xcode (ou configurer les profiles manuels).
  2. Renseigner Privacy Nutrition Labels dans App Store Connect (données collectées, utilisation).
  3. Justifier chaque NSUsageDescription dans Info.plist (caméra, micro, localisation…).
  4. Build via Product → Archive → Distribute App → App Store Connect.
  5. TestFlight : group interne (jusqu'à 100 personnes, immédiat), groupe externe (review Beta ~24 h).
  6. Délai de review App Review : ~24–48 h en 2026 (accéléré possible via API).

Anti-patterns et pièges

PiègeConséquenceRemède
URLSession sur le main threadUI geléeasync/await ou .background queue
Capture self forte dans closure @escapingFuite mémoire[weak self] systématique
@ObservedObject sur un objet créé dans la ViewRe-init à chaque rebuildPasser via @StateObject ou @Observable @State
Core Data sans NSManagedObjectContext per-threadCrash aléatoireperformAndWait ou @ModelActor (SwiftData)
UserDefaults pour tokens/mdpFaille sécurité (lisible)Keychain uniquement
Lancer Task { } sans annulationTasks zombiesStocker dans @State var task: Task<…>? et .cancel() dans onDisappear
GeometryReader enveloppant toutLayout cassé, rebuild excessifLimiter au besoin précis, préférer containerRelativeFrame (iOS 17)

Bonnes pratiques 2026