Swift - Möglichkeiten Daten zu speichern

Aus Wikizone
Wechseln zu: Navigation, Suche

Einführung

Es gibt mehrere Möglichkeiten, Benutzerdaten wie Notizen oder Einkaufslisten in einer iPhone-App, die in Swift geschrieben ist, zu speichern. Einige der populärsten Optionen sind:

  1. User Defaults: Dies ist eine einfache und einfach zu verwendende Methode zum Speichern von kleinen Mengen an Benutzerdaten. Es speichert Daten im Schlüssel-Wert-Paar-Format und die Daten werden automatisch gespeichert, wenn die App geschlossen wird oder im Hintergrund läuft.
  2. Core Data: Core Data ist ein Framework für objektrelationales Mapping (ORM), mit dem Entwickler auf das Datenmodell der App zugreifen können. Es ist eine leistungsstarke und flexible Lösung zum Speichern und Abrufen großer Datenmengen und bietet Funktionen wie Rückgängig- und Wiederholen-Funktionen, Änderungsverfolgung und Datenvalidierung.
  3. Realm: Realm ist ein von Dritten bereitgestelltes ORM, das Core Data ähnlich ist, aber im Allgemeinen als leichter und schneller gilt.
  4. File I/O: Dies ist die traditionelle Methode zum Speichern von Daten in iOS-Apps. Entwickler können Daten mithilfe des Foundation-Frameworks von und auf das lokale Dateisystem der App lesen und schreiben.
  5. Cloud Service: Durch die Verwendung von Cloud-Service-Anbietern wie AWS, Firebase oder Firestore kann die Daten auf deren Servern gespeichert werden, anstatt auf dem lokalen Gerät. Dieser Ansatz eignet sich, wenn die Daten unter verschiedenen Benutzern geteilt werden oder auf mehreren Geräten zugänglich sein sollen.

Letztlich hängt die Wahl der Methode zum Speichern von Daten von den spezifischen Anforderungen Ihrer App ab, einschließlich der Menge an zu speichernder Daten, der Komplexität des Datenmodells und den Anforderungen an die Datensynchronisierung und Sicherung.

Beispiele

Speichern von JSON Daten mit File I/O

struct Setlist: Codable {
    var title: String = "Default"
    var songs = [Song]()

    mutating func addSong(title: String, frequency: Float){
        print("Setlist::addSong")
        songs.append(Song(title:title, frequency: frequency))
    }
}

struct Song: Codable {
    let title: String
    let frequency: Float
}

// convert Setlist to json Data
let setlist = Setlist()
setlist.addSong(title: "song1", frequency: 44.1)
let encoder = JSONEncoder()
guard let encoded = try? encoder.encode(setlist) else {
    print("Encoding Failed")
    return
}

// write json Data to file
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documentsURL.appendingPathComponent("setlist.json")
do {
    try encoded.write(to: fileURL, options: .atomic)
} catch {
    print("Error saving file: \(error)")
}

In diesem Beispiel wird die JSONEncoder Klasse verwendet, um eine Instanz des Structs Setlist in ein JSON-kodiertes Data-Objekt umzuwandeln. Das erzeugte JSON-kodierte Data wird in einer Datei gespeichert . In diesem Fall wird die Datei "setlist.json" im Dokumentenverzeichnis des Geräts gespeichert. Es ist zu beachten, dass es je nach Anwendungsfall sinnvoll sein kann, die Daten an einem anderen Ort oder in einem anderen Format zu speichern.

Speichern von Daten mit CoreData

Das Speichern von Instanzen des Structs Setlist in Core Data erfordert einige zusätzliche Schritte im Vergleich zu User Defaults, da Core Data ein umfangreicheres Framework ist. Hier ist ein Beispiel dafür, wie dies mit Swift und Core Data erreicht werden kann:

import CoreData

struct Setlist {
    var title: String = "Default"
    var songs = [Song]()
    var objectID: NSManagedObjectID?
    
    mutating func addSong(title: String, frequency: Float){
        print("Setlist::addSong")
        songs.append(Song(title:title, frequency: frequency))
    }
}

struct Song {
    let title: String
    let frequency: Float
}

// To Save
func saveSetlist(_ setlist: Setlist) {
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }
    let managedContext = appDelegate.persistentContainer.viewContext
    let setlistEntity = NSEntityDescription.entity(forEntityName: "SetlistEntity", in: managedContext)!
    let setlistMO = NSManagedObject(entity: setlistEntity, insertInto: managedContext)
    setlistMO.setValue(setlist.title, forKey: "title")
    
    var songMOs = [NSManagedObject]()
    for song in setlist.songs {
        let songEntity = NSEntityDescription.entity(forEntityName: "SongEntity", in: managedContext)!
        let songMO = NSManagedObject(entity: songEntity, insertInto: managedContext)
        songMO.setValue(song.title, forKey: "title")
        songMO.setValue(song.frequency, forKey: "frequency")
        songMOs.append(songMO)
    }
    setlistMO.setValue(NSOrderedSet(array: songMOs), forKey: "songs")
    setlist.objectID = setlistMO.objectID
    do {
        try managedContext.save()
    } catch let error as NSError {
        print("Could not save. \(error), \(error.userInfo)")
    }
}

// To Retrieve
func fetchSetlist(with objectID: NSManagedObjectID) -> Setlist? {
    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return nil }
    let managedContext = appDelegate.persistentContainer.viewContext
    let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "SetlistEntity")
    fetchRequest.predicate = NSPredicate(format: "self == %@", objectID as CVarArg)
    do {
        let setlistMO = try managedContext.fetch(fetchRequest).first
        guard let title = setlistMO?.value(forKey: "title") as? String, let songsMO = setlistMO?.value(forKey: "songs") as? Set<NSManagedObject> else {
            return nil
        }
        var songs = [Song]()
        forsongMO in songsMO {
             let title = songMO.value(forKey: "title") as? String, let frequency = songMO.value(forKey: "frequency") as? Float else {
                 continue
             }
             songs.append(Song(title: title, frequency: frequency))
        }
        var setlist = Setlist(title: title, songs: songs)
        setlist.objectID = objectID
        return setlist
    } catch let error as NSError {
        print("Could not fetch. (error), (error.userInfo)")
    return nil
    }
}

Dieses Beispiel verwendet Core Data, um Instanzen des Structs Setlist zu speichern und abzurufen. Es verwendet NSManagedObjects, um die Entitäten von Setlist und Song im Datenmodell darzustellen und die Beziehungen zwischen ihnen zu verwalten. Bitte beachten Sie, dass es hier nur um ein Beispiel geht und dass Core Data viele weitere Funktionen bietet, die je nach Anwendungsfall sinnvoll sein können.

Speichern mit SQLite und FMDB

Das Speichern von Instanzen des Structs Setlist in SQLite erfordert die Verwendung eines Wrapper-Libraries die eine einfachere Anbindung von SQLite Datenbanken in Swift ermöglicht, wie z.B FMDB. Hier ist ein Beispiel dafür, wie dies mit Swift und FMDB (OpenSoure Lib) erreicht werden kann:

import FMDB

struct Setlist {
    var title: String = "Default"
    var songs = [Song]()
    var id: Int32?
    mutating func addSong(title: String, frequency: Float){
        print("Setlist::addSong")
        songs.append(Song(title:title, frequency: frequency))
    }
}

struct Song {
    let title: String
    let frequency: Float
}

let databaseQueue = FMDatabaseQueue(url: try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("db.sqlite"))

func createTable() {
    databaseQueue.inDatabase { db in
        do {
            try db.executeUpdate("CREATE TABLE Setlist (id INTEGER PRIMARY KEY, title TEXT);", values: nil)
            try db.executeUpdate("CREATE TABLE Song (id INTEGER PRIMARY KEY, title TEXT, frequency REAL, setlist_id INTEGER, FOREIGN KEY(setlist_id) REFERENCES Setlist(id));", values: nil)
        } catch {
            print(error)
        }
    }
}

// To Save
func saveSetlist(_ setlist: Setlist) {
    databaseQueue.inDatabase { db in
        do {
            try db.executeUpdate("INSERT INTO Setlist (title) VALUES (?);", values: [setlist.title])
            setlist.id = db.lastInsertRowId
            for song in setlist.songs {
                try db.executeUpdate("INSERT INTO Song (title, frequency, setlist_id) VALUES (?, ?, ?);", values: [song.title, song.frequency,setlist.id])
            }
        } catch {
            print(error)
        }
    }
}

// To Retrive
func fetchSetlist(with id: Int32) -> Setlist? {
    var setlist: Setlist?
    databaseQueue.inDatabase { db in
        do {
            let resultSetlist = try db.executeQuery("SELECT * FROM Setlist WHERE id = ?", values: [id])
            if resultSetlist.next() {
                setlist = Setlist(title: resultSetlist.string(forColumn: "title")!)
                setlist!.id = id
                let resultSongs = try db.executeQuery("SELECT * FROM Song WHERE setlist_id = ?", values: [id])
                while resultSongs.next() {
                    let song = Song(title: resultSongs.string(forColumn: "title")!,frequency: resultSongs.double(forColumn: "frequency"))
                    setlist!.songs.append(song)
                }
            }
            resultSetlist.close()
         } catch {
             print(error)
         }
    }
    return setlist
}

Dieses Beispiel verwendet FMDB, um eine Verbindung zur SQLite-Datenbank herzustellen und Abfragen auszuführen, um Instanzen des Structs Setlist zu speichern und abzurufen. Beachten Sie, dass die Beispielstrukturen und die Datenbanktabellen beide eine ID-Eigenschaft haben, um die Beziehungen zwischen Setlist und Song zu verwalten. Es gibt auch andere Bibliotheken wie SQLite.swift die auch SQLite in Swift erleichtern. Ich empfehle Ihnen, sich mit den Best Practices für die Verwendung von SQLite in iOS-Apps vertraut zu machen, um sicherzustellen, dass Ihre Anwendung sicher und performant ist.