mit XCode getestet 7.1.1, Swift 2.1 & iOS 9
Sie haben ein paar Optionen, um Ihre (Array von) benutzerdefinierte Objekte zu speichern:
- NSUserDefaults: to Store App Einstellungen, Voreinstellungen, Benutzereinstellungen:
- NSKeyedArchiver: für allgemeine Datenspeicherung
- Daten Kern: für komplexere Datenspeicher (Datenbank wie)
lasse ich Kerndaten aus dieser Diskussion, aber möchte Ihnen zeigen, warum Sie besser NSKeyedArchiver über NSUserDefaults verwenden sollten.
Ich habe Ihre Player-Klasse aktualisiert und Methoden für beide Optionen bereitgestellt. Obwohl beide Optionen funktionieren, wenn Sie die 'load & save' Methoden vergleichen, sehen Sie, dass NSKeydArchiver weniger Code benötigt, um Arrays von benutzerdefinierten Objekten zu behandeln. Auch mit NSKeyedArchiver können Sie einfach Dinge in separaten Dateien speichern, anstatt sich um eindeutige "Schlüssel" -Namen für jede Eigenschaft kümmern zu müssen.
import UIKit
import Foundation
// a custom class like the one that you want to archive needs to conform to NSCoding, so it can encode and decode itself and its properties when it's asked for by the archiver (NSKeydedArchiver or NSUserDefaults)
// because of that, the class also needs to subclass NSObject
class Player: NSObject, NSCoding {
var name: String = ""
// designated initializer
init(name: String) {
print("designated initializer")
self.name = name
super.init()
}
// MARK: - Conform to NSCoding
func encodeWithCoder(aCoder: NSCoder) {
print("encodeWithCoder")
aCoder.encodeObject(name, forKey: "name")
}
// since we inherit from NSObject, we're not a final class -> therefore this initializer must be declared as 'required'
// it also must be declared as a 'convenience' initializer, because we still have a designated initializer as well
required convenience init?(coder aDecoder: NSCoder) {
print("decodeWithCoder")
guard let unarchivedName = aDecoder.decodeObjectForKey("name") as? String
else {
return nil
}
// now (we must) call the designated initializer
self.init(name: unarchivedName)
}
// MARK: - Archiving & Unarchiving using NSUserDefaults
class func savePlayersToUserDefaults(players: [Player]) {
// first we need to convert our array of custom Player objects to a NSData blob, as NSUserDefaults cannot handle arrays of custom objects. It is limited to NSString, NSNumber, NSDate, NSArray, NSData. There are also some convenience methods like setBool, setInteger, ... but of course no convenience method for a custom object
// note that NSKeyedArchiver will iterate over the 'players' array. So 'encodeWithCoder' will be called for each object in the array (see the print statements)
let dataBlob = NSKeyedArchiver.archivedDataWithRootObject(players)
// now we store the NSData blob in the user defaults
NSUserDefaults.standardUserDefaults().setObject(dataBlob, forKey: "PlayersInUserDefaults")
// make sure we save/sync before loading again
NSUserDefaults.standardUserDefaults().synchronize()
}
class func loadPlayersFromUserDefaults() -> [Player]? {
// now do everything in reverse :
//
// - first get the NSData blob back from the user defaults.
// - then try to convert it to an NSData blob (this is the 'as? NSData' part in the first guard statement)
// - then use the NSKeydedUnarchiver to decode each custom object in the NSData array. This again will generate a call to 'init?(coder aDecoder)' for each element in the array
// - and when that succeeded try to convert this [NSData] array to an [Player]
guard let decodedNSDataBlob = NSUserDefaults.standardUserDefaults().objectForKey("PlayersInUserDefaults") as? NSData,
let loadedPlayersFromUserDefault = NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSDataBlob) as? [Player]
else {
return nil
}
return loadedPlayersFromUserDefault
}
// MARK: - Archivig & Unarchiving using a regular file (using NSKeyedUnarchiver)
private class func getFileURL() -> NSURL {
// construct a URL for a file named 'Players' in the DocumentDirectory
let documentsDirectory = NSFileManager().URLsForDirectory((.DocumentDirectory), inDomains: .UserDomainMask).first!
let archiveURL = documentsDirectory.URLByAppendingPathComponent("Players")
return archiveURL
}
class func savePlayersToDisk(players: [Player]) {
let success = NSKeyedArchiver.archiveRootObject(players, toFile: Player.getFileURL().path!)
if !success {
print("failed to save") // you could return the error here to the caller
}
}
class func loadPlayersFromDisk() -> [Player]? {
return NSKeyedUnarchiver.unarchiveObjectWithFile(Player.getFileURL().path!) as? [Player]
}
}
diese Klasse I getestet haben, wie (single view app, in dem viewDidLoad Verfahren des Viewcontroller) folgt
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// create some data
let player1 = Player(name: "John")
let player2 = Player(name: "Patrick")
let playersArray = [player1, player2]
print("--- NSUserDefaults demo ---")
Player.savePlayersToUserDefaults(playersArray)
if let retreivedPlayers = Player.loadPlayersFromUserDefaults() {
print("loaded \(retreivedPlayers.count) players from NSUserDefaults")
print("\(retreivedPlayers[0].name)")
print("\(retreivedPlayers[1].name)")
} else {
print("failed")
}
print("--- file demo ---")
Player.savePlayersToDisk(playersArray)
if let retreivedPlayers = Player.loadPlayersFromDisk() {
print("loaded \(retreivedPlayers.count) players from disk")
print("\(retreivedPlayers[0].name)")
print("\(retreivedPlayers[1].name)")
} else {
print("failed")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
wie oben gesagt, erzeugen beide Verfahren das gleiche Ergebnis
Auch In einer realen Anwendung könnten Sie eine bessere Fehlerbehandlung in der Anwendung durchführen, da die Archivierung & fehlschlagen kann.
nur ein Summen zu schicken, vor kurzem habe ich ein Beitrag im Swift Forum beantwortet eine ähnliche Frage, hoffe es hilft. https://devforums.apple.com/message/1044432#1044432 – vladof81
@ Vladof81, das funktioniert perfekt :). – sabiland