Alle Open-Source-Pakete
ArchitekturSwift

iOS-Offline-First-Framework

Ein lokaler Store für Swift-Apps, der auch offline weiterläuft und sich mit deinem Server synchronisiert.

2SterneNeueste Version: v2.1.0 · Aktualisiert: 21. Oktober 2026
Plattformen:iOS 17macOS 14tvOS 17watchOS 10visionOS 1

Lizenz: MIT

Tests: 375Gemessen:

Wie gemessen wurde

swift test at tag v2.1.0 (205c546) on macOS 27.0.1 with Swift 6.4: 375 tests in 75 suites passed; run again with OFFLINE_FIRST_NODE set so the three reference-server suites ran, with the same result; the two end-to-end suites that need OFFLINE_FIRST_SERVER_URL were skipped. npm test of Examples/SyncServer on Node 26.11.1: 28 tests passed. The README states 372 tests on an iPhone simulator, which was not re-run here.

Zusammenfassung

  • Ein Swift-Package, das Datensätze in einer Datei auf dem Gerät hält, jede Änderung in eine Warteschlange stellt und mit jedem Server synchronisiert, wenn eine Verbindung besteht.
  • Version 2.1.0 behebt die Data-Integrity-Befunde zweier Audits von 2.0.0 und ihrer späteren Runden, jeweils mit einem Test, der vor dem Fix fehlschlug.
  • Nur eine vom Store gelesene Push-Antwort entscheidet eine Änderung endgültig; nach einer verlorenen oder unlesbaren Antwort bleibt die Änderung bis zum nächsten Push ungeklärt.
  • Eine neue Server-Epoch zählt nur, wenn zwei Requests desselben Syncs sie melden, und der Store geht nie zu einer früheren Epoch zurück.
  • Antworten von einem anderen Deployment hinter derselben URL werden nur teilweise abgedeckt; acht offene Issues beschreiben jeden Fall und den Weg zurück.

Ein Swift-Package, das die Datensätze einer App in einer Datei auf dem Gerät hält, jede Änderung in eine Warteschlange stellt und mit einem Server synchronisiert, sobald eine Verbindung besteht. Speichern wartet nie auf das Netzwerk. Wenn sich derselbe Datensatz auf zwei Geräten geändert hat, entscheidet eine von dir gewählte Conflict Policy, was erhalten bleibt. Es funktioniert mit jedem Backend: einem kleinen HTTP-Protokoll mit einem Referenzserver, deiner eigenen API über ein Protocol mit zwei Funktionen, oder einem In-Memory-Server für Tests und Previews. Das Package hat keine Abhängigkeiten.

Funktionen

  • Schreibvorgänge, die nie warten: save und delete gehen in eine atomar geschriebene Datei und eine dauerhafte Warteschlange ausstehender Änderungen; fehlgeschlagene Pushes werden mit exponential backoff und jitter wiederholt und nach dem letzten Versuch geparkt, statt verloren zu gehen
  • Conflict Policies: last write wins auf hybrid logical clock-Zeitstempeln, server wins, client wins, dein eigenes merge, oder konfliktfreie Felder (LWWRegister, LWWMap, GCounter, PNCounter, ORSet)
  • Jedes Backend: HTTPSyncTransport spricht ein dokumentiertes JSON-Protokoll mit zwei Endpoints, das der Node-Referenzserver in Examples/SyncServer implementiert; jede andere API passt hinter das SyncTransport-Protocol
  • SwiftUI: SyncedCollection ist ein @Observable-Modell eines Stores; automatischer Sync läuft, wenn das Gerät online geht, und kurz nach lokalen Änderungen
  • Daten im Ruhezustand: AES-GCM-Dateiverschlüsselung mit einem im Keychain aufbewahrten Schlüssel, Record-Schema-Migrationen und eine Policy für beschädigte Dateien
  • Begrenzt gegen Server, die sich falsch verhalten, und gegen beschädigte Dateien: Outbox-Größe, Pull-Seiten, Pushes pro Record, Größen von Response, Revision und Change-Token, sowie Dateinamen, die ihr Verzeichnis nicht verlassen können
  • Erklärte Garantien: ein vom Server akzeptierter Write wird nie durch eine veraltete Kopie ersetzt, nur eine gelesene Antwort entscheidet eine Änderung endgültig, und eine neue Server-Epoch zählt nur, wenn zwei Requests desselben Syncs sie melden; der Artikel Guarantees and limits beschreibt, was ein Server dafür tun muss
  • Getestet mit Swift Testing: 375 Tests auf macOS und 372 auf einem iPhone-Simulator, einschließlich Property-Tests, die jede Conflict Policy durch verlorene Requests, Neustarts, Token-Resets, veraltete Seiten und Abstürze laufen lassen, sowie End-to-End-Suites in CI gegen den Referenzserver, der selbst 28 Node-Tests hat

Schnellstart

swift
1import OfflineFirstFramework
2import SwiftUI
3 
4/// A to-do item. Any `Codable` struct with an `id` can be a record.
5struct Todo: SyncableRecord, Hashable {
6 var id = UUID()
7 var title: String
8 var isDone = false
9}
10 
11enum TodoStore {
12 // InMemorySyncServer stands in for a backend; swap in HTTPSyncTransport or your own
13 // SyncTransport when you have one. The initializer throws only for an invalid name or a
14 // store file it cannot read.
15 static let shared = try! OfflineStore<Todo>(name: "todos", transport: InMemorySyncServer<Todo>())
16}
17 
18struct ContentView: View {
19 @State private var todos = SyncedCollection(store: TodoStore.shared)
20 @State private var newTitle = ""
21 
22 var body: some View {
23 NavigationStack {
24 List {
25 TextField("New to-do", text: $newTitle)
26 .onSubmit {
27 let todo = Todo(title: newTitle)
28 newTitle = ""
29 Task { await todos.save(todo) }
30 }
31 ForEach(todos.records) { todo in
32 Text(todo.title)
33 }
34 }
35 .navigationTitle("To-dos")
36 .toolbar {
37 Text("\(todos.pendingCount) to sync")
38 }
39 .refreshable { await todos.sync() }
40 }
41 }
42}

Installation

Xcode (File › Add Package Dependencies…)

text
1https://github.com/muhittincamdali/iOS-Offline-First-Framework.git

Swift Package Manager

swift
1dependencies: [
2 .package(url: "https://github.com/muhittincamdali/iOS-Offline-First-Framework.git", from: "2.1.0")
3],
4targets: [
5 .target(name: "YourApp", dependencies: [
6 .product(name: "OfflineFirstFramework", package: "iOS-Offline-First-Framework")
7 ])
8]

Screenshots

Die Beispiel-App OfflineTodos auf einem iPhone-Simulator, alle To-dos synchronisiert, Light Mode
Die Beispiel-App OfflineTodos offline, zwei Änderungen warten auf Sync, Light Mode
Die Beispiel-App OfflineTodos, alle To-dos synchronisiert, Dark Mode
Die Beispiel-App OfflineTodos offline, zwei Änderungen warten auf Sync, Dark Mode

Zeitleiste

  1. Repository eröffnet als eine Sammlung von Manager-Klassen; der Sync-Manager simulierte seine Ergebnisse und kontaktierte nie einen Server

  2. Version 1.0.0 getaggt; ihr Manifest deklarierte einen fehlenden Resources-Ordner und unsafe Compiler-Flags, weshalb sie nicht nach Version als Dependency hinzugefügt werden konnte

  3. Verschlankt: RxSwift und CocoaLumberjack durch Combine ersetzt, Manager verkürzt, Sync weiterhin ein Platzhalter

  4. Von Grund auf neu aufgebaut als Offline-Store mit einer Sync-Engine und als 2.0.0 veröffentlicht; das Package SwiftPersistence wurde eingegliedert und archiviert, keine API der 1.0.0 blieb erhalten. Zwei Audits von 2.0.0 am selben Tag listeten elf unbedingt zu behebende Data-Integrity-Befunde auf

  5. v2.1.0: Data-Integrity-Release nach fünfzehn Runden von Fixes und Prüfungen; Runde 14 und 15 wurden nach Regressionen verworfen, der Release-Branch ging auf den letzten Commit von Runde 13 zurück, und acht bekannte Einschränkungen wurden als Issues eingetragen

Links

Zusammengeführte Repositories

Verwandter Artikel

Artikel lesen