iPhone-App-Entwicklung
Konventionen, destilliert aus der Auslieferung einer SwiftUI-App von Anfang bis Ende – von einem leeren Repository bis zu einem signierten Build, der auf einem physischen Telefon läuft.
Angenommener Kontext: Kleine Apps, ein kostenloses persönliches Apple-Konto, keine App-Store-Verteilung. Ein bezahltes Entwicklerteam ändert einige der folgenden Regeln – der Abschnitt zur Signierung markiert, welche.
Architektur-Standards
- SwiftUI + SwiftData, local-first. Eine kleine App sollte keinen erreichbaren Server benötigen. Daten leben auf dem Gerät in SwiftData; Netzwerkaufrufe sind nur für öffentliche APIs vorgesehen (https, keine ATS-Ausnahmen). Wenn die App als Thin Client für ein bestehendes Backend startet, rechnen Sie mit dem „Können wir den Server loswerden?“-Pivot – portieren Sie die Logik des Backends in reine Swift-Funktionen über die SwiftData-Arrays, anstatt einen Server für ein Telefon am Leben zu erhalten.
- SwiftData-Modellierung: Echte Objektbeziehungen, keine Integer-
Fremdschlüssel.
@Attribute(.unique)für natürliche Schlüssel. SwiftData- To-many-Arrays bewahren keine Reihenfolge – fügen Sie einsortOrder-Feld und einsortedX-Berechnetes-Eigenschaft hinzu, wenn die Reihenfolge wichtig ist. Datumsangaben alsDate, nicht als Strings. Enums alsString-Raw-Codable. - Schemaänderungen bei einer laufenden Installation: Das Hinzufügen von Modellen oder
optionalen Eigenschaften migriert beim nächsten Start automatisch; Umbenennungen,
Löschungen oder Typänderungen ohne einen
SchemaMigrationPlanführen dazu, dass der Speicher nicht geöffnet werden kann – die bereitgestellte App stürzt dann beim Start ab, während die Daten des Benutzers intakt, aber nicht zugänglich sind. Erneute Bereitstellungen (gleiche Bundle-ID) löschen nie den Datencontainer; nur das Löschen der App tut das. Planen Sie einen Export-/Backup-Pfad, bevor Benutzer echte Daten ansammeln. - Views kommunizieren direkt mit
@Query+modelContext. Keine Store-/Repository- Schicht, es sei denn, etwas benötigt wirklich Caching. Reine Logik (Matching, Parsen, Importieren) lebt in Enum-Namespaces (enum Foo { static func … }) – testbar ohne UI. - Deployment-Ziel: current-major-minus-one, es sei denn, eine benötigte API
sagt etwas anderes. Ein Target,
TARGETED_DEVICE_FAMILY: "1", Hochformat, es sei denn, die App verdient eine Rotation.
Projektgenerierung – XcodeGen, niemals ein eingechecktes .xcodeproj
ios/project.yml ist die Quelle der Wahrheit; das .xcodeproj-Bundle wird
generiert und gitignored (gitignoren Sie auch ios/build/ und das generierte
Info.plist). Regenerieren Sie es nach dem Hinzufügen oder Entfernen von Dateien – xcodegen
durchläuft das Quellverzeichnis, sodass Bearbeitungen vorhandener Dateien nichts benötigen.
- Installieren Sie xcodegen aus dem GitHub-Release-Zip (
PREFIX=~/.local ./install.sh) – kein Homebrew erforderlich, funktioniert auf einem gesperrten Mac. - Unit-Test-Targets benötigen
GENERATE_INFOPLIST_FILE: YES, sonst schlägt das Test- Bundle beim Signieren fehl. - Definieren Sie das Schema in
project.ymlmitgatherCoverageData: trueund dem angehängten Test-Target, sonst hatxcodebuild testnichts auszuführen.
Der Entwicklungs-Loop – Nur CLI, Simulator standardmäßig
Jedes iOS-Projekt erhält diese Makefile-Ziele. Parametrisieren Sie den Sim-
namen, die Geräte-ID und die Team-ID als ?=-Variablen, sodass nichts
maschinenspezifisch hartcodiert ist:
| Ziel | Was |
|---|---|
ios-project | xcodegen generieren + in Xcode öffnen |
ios-sim | Unsigned für Simulator bauen, Sim booten, installieren, starten – der Standard-Loop; null Signierung, null Passwort-Eingabeaufforderungen |
ios-sim-seed | ios-sim, dann Neustart mit --seed-demo |
ios-test | xcodebuild test im Simulator + xccov pro-Target-Coverage-Bericht |
ios-deploy | Signierter Build für das angeschlossene Telefon + devicectl installieren + starten |
ios-test gibt bei jedem Durchlauf die Coverage aus – die Hausregel ist, dass
make test immer die Zahl anzeigt, ohne dass ein festgelegter Grenzwert angehängt ist.
Simulator-Builds: CODE_SIGNING_ALLOWED=NO ist in Ordnung für
generic/platform=iOS-Kompilierungsprüfungen; Simulator-Ziele benötigen überhaupt
keine Signierung. Denken Sie daran, dass Simulator und Telefon getrennte Datenbanken haben.
Debug-Komfort, der sich immer auszahlt
- Beispieldaten-Seeder (
DemoData.swift,#if DEBUG): Kuratierter Datensatz mit Ablauf-/Datumsfeldern, die relativ zu.nowberechnet werden, idempotent (erneutes Ausführen fügt nichts hinzu), auf drei Arten exponiert – eine Schaltfläche im Einstellungsabschnitt „Entwickler“, ein--seed-demo-Startargument, das überProcessInfo.processInfo.argumentsgeprüft wird, und dasios-sim-seed-Ziel. Fügen Sie einen Test hinzu, der bestätigt, dass der Seed die Hauptzustände der App durchläuft, damit er nicht stillschweigend veraltet. - Durchsuchbare Picker statt Rad-Picker, sobald eine Liste ein paar Dutzend Einträge überschreiten kann (importierte Datensätze garantieren dies).
- Kamera/Scanner-Funktionen: Prüfen Sie
DataScannerViewController.isSupported && .isAvailableund liefern Sie einen Fallback für manuelle Eingabe – es funktioniert auch im Simulator elegant.
Testen
- XCTest im Simulator; beim Portieren von Logik aus einem anderen Stack portieren Sie auch die Referenztestsuite, damit die Semantik sprachübergreifend festgelegt ist.
- Gemeinsamer Helfer:
makeInMemoryContext()erstellt einen ModelContainer mitisStoredInMemoryOnly: true;@MainActor-Testklassen mitsetUp() async throws. - Lesen Sie Bestanden/Nicht bestanden aus dem Ergebnis-Bundle aus:
xcrun xcresulttool get test-results summary --path build/test.xcresult.
Signierung & Bereitstellung – Die Tücken (jede hat echte Zeit gekostet)
- Die Team-ID ist NICHT die Klammer in
security find-identity– das ist die eigene ID des Zertifikats. Holen Sie die echte Team-ID aus einem Provisioning-Profil (security cms -D -i <profile>→ dasapplication-identifier-Präfix) oder dem Bereich „Accounts“ in Xcode. Die falsche ID ergibt einen irreführenden Fehler „No Account for Team“. -allowProvisioningUpdateskann die Apple-ID-Sitzung von einem einfachen Terminal aus nicht sehen. Erstellen Sie das Provisioning-Profil einmal in der Xcode-GUI (Signing & Capabilities → Team setzen → Erneut versuchen); danach funktionieren reine CLI-Builds mitDEVELOPMENT_TEAM=<id> CODE_SIGN_STYLE=Automatic.- First-Device-Tanz: Aktivieren Sie den Entwicklermodus auf dem Telefon (Einstellungen → Datenschutz & Sicherheit; der Schalter erscheint erst, nachdem Xcode mit dem Gerät gesprochen hat; Neustarts) → vertrauen Sie dem Computer → vertrauen Sie nach der ersten Installation dem Entwicklerzertifikat (Einstellungen → Allgemein → VPN & Geräteverwaltung). Xcodes Fehler „your team has no devices“ bedeutet normalerweise, dass der Registrierungsversuch stattfand, während das Telefon neu startete oder gesperrt war – wählen Sie das Telefon als Ausführungsziel und wiederholen Sie den Vorgang.
- Schlüsselbund-Abfragen bei jedem Build: Klicken Sie einmal auf Immer erlauben im Codesign-Schlüsselbunddialog; die Passwortabfragen pro Build hören auf.
- Grenzen des kostenlosen persönlichen Teams: Installationen laufen nach 7 Tagen ab (erneutes Bereitstellen zum erneuten Signieren), maximal 3 sideloaded Apps, keine Push-Benachrichtigungen, keine App-Gruppen. Ein kostenpflichtiges Team hebt alle vier Einschränkungen auf – wenn die App Push- oder App-Gruppen benötigt, ist das der Auslöser, eines zu besorgen.
- CLI für Bereitstellung/Start:
xcrun devicectl list devices,xcrun devicectl device install app --device <id> <path>.app,… device process launch --device <id> <bundle-id>.
Fresh-Mac-Bootstrap (headless-freundlich)
- Xcode aus dem App Store (öffnen Sie
macappstore://apps.apple.com/app/id497799835). sudo xcode-select -s /Applications/Xcode.app && sudo xcodebuild -license accept && sudo xcodebuild -runFirstLaunch– benötigt einen Admin; bei einem Standardkonto korrigieren Sie zuerst die Admin-Mitgliedschaft (su admin -c 'sudo dseditgroup -o edit -a <user> -t user admin').xcodebuild -downloadPlatform iOS– das iOS SDK + die Simulator-Laufzeitumgebung ist seit Xcode 15 ein separater Multi-GB-Download; Builds schlagen fehl mit „iOS X.Y is not installed“ ohne ihn.DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developerfunktioniert als pro-Befehl-Override, bevor xcode-select repariert ist.- xcodegen aus dem Release-Zip in
~/.local(siehe oben).
Begleit-Clients für eine bestehende Server-App – Sync-Stufen
Wenn die iPhone-App zu einer App hinzukommt, die bereits einen Server + Web-App hat (der Server bleibt die einzige Quelle der Wahrheit), wählen Sie die günstigste Stufe, die der Anwendungsfall des Telefons zulässt. Fragen Sie zuerst: „Muss das Telefon außerhalb des Netzwerks des Servers arbeiten?“ und „Erfassen oder vollständige Bearbeitung?“:
- Thin Client – kein lokaler Speicher; das Telefon ruft die vorhandene API auf. Richtig, wenn Konnektivität gegeben ist.
- Snapshot-Mirror – SwiftData-Cache wird vollständig aus einem vollständigen JSON-Pull neu aufgebaut (Einzelbenutzer-DBs sind klein). Kein Merging → keine Tombstones, keine zeilenweisen Zeitstempel, keine Konflikt-Engine; serverseitige Löschungen/Bearbeitungen kommen kostenlos an. Fotos werden per URL-Cache lazy geladen.
- Scoped Outbox – Offline-Erstellung beschränkt auf Kinder von Datensätzen, die bereits existieren (Protokolle, Notizen, Fotos zu bekannten Eltern). Eltern tragen echte Server-IDs, daher ist der Replay ein einfacher API-Aufruf – Integer-PK-Schemata benötigen keine UUID-Migration. Sync-Ereignis = Push-Outbox, dann Snapshot-Pull (eigene Erfassungen kommen bestätigt zurück). Zeigen Sie 404-Waisen an („N konnte nicht synchronisiert werden“), verwerfen Sie sie niemals stillschweigend. Zeigen Sie ein ausstehendes Abzeichen + den Zeitstempel der letzten Synchronisierung an. Auto-Sync nur, wenn die gepinnte Serveradresse des Benutzers antwortet – durchsuchen Sie niemals fremde Netzwerke.
- Vollständiger bidirektionaler Sync (UUIDs, Tombstones, Delta-Endpunkte,
LWW/CRDT) – nur mit Belegen, dass die Stufen 1–3 nicht ausreichen; die Überarbeitung
beginnt serverseitig (client-erstellbare UUIDs, idempotente Upserts,
changes?since=+ Tombstones), bevor irgendein Swift geschrieben wird.
Die Stufen 2 und 3 lassen sich gut kombinieren: Ein Snapshot-Mirror zum Lesen plus eine Scoped Outbox zum Erfassen deckt „funktioniert zu Hause, erfasst überall“ ab, ohne die Mechanik der Stufe 4.
Projekthygiene
- Maschinen- und gerätespezifische Fakten (Team-ID, Geräte-UDID, Sim-Name) gehen
in Makefile-
?=-Variablen oder die eigenen Notizen des Projekts – niemals hartcodiert in Skripten und niemals in dieser Skill-Datei. - Führen Sie für jedes in Arbeit befindliche Slice ein Dokument
plans/<topic>.mdund eineCHANGELOG.mdauf oberster Ebene für das, was ausgeliefert wurde; verschieben Sie einen Plan nachplans/done/<topic>.mdmit einem Header „Delivered in – “, sobald er abgeschlossen ist. - Dimensionieren Sie jeden Planslice so, dass er mit etwas endet, das auf einem Telefon oder Simulator ausgeführt werden kann. Ein iOS-Slice, der nicht gestartet werden kann, kann nicht überprüft werden.