如何在Swift中存档和取消存档自定义对象?或者如何将自定义对象保存到Swift中的NSUserDefaults?

问题描述:

我有一个类如何在Swift中存档和取消存档自定义对象?或者如何将自定义对象保存到Swift中的NSUserDefaults?

class Player { 

    var name = "" 

    func encodeWithCoder(encoder: NSCoder) { 
     encoder.encodeObject(name) 
    } 

    func initWithCoder(decoder: NSCoder) -> Player { 
     self.name = decoder.decodeObjectForKey("name") as String 
     return self 
    } 

    init(coder aDecoder: NSCoder!) { 
     self.name = aDecoder.decodeObjectForKey("name") as String 
    } 

    init(name: String) { 
     self.name = name 
    } 
} 

,我想它连载并保存到用户的默认值。所有的

首先我不知道如何正确地写编码和解码器。所以对于init我写了两种方法。

当我尝试执行此代码:

func saveUserData() { 
    let player1 = Player(name: "player1") 
    let myEncodedObject = NSKeyedArchiver.archivedDataWithRootObject(player1) 
    NSUserDefaults.standardUserDefaults().setObject(myEncodedObject, forKey: "player") 
} 

应用程序崩溃,我得到这个消息:

*** NSForwarding: warning: object 0xebf0000 of class '_TtC6GameOn6Player' does not implement methodSignatureForSelector: -- trouble ahead 

我该怎么办错了吗?

+2

只需发送一个时髦,最近我做了在Swift论坛上回复类似问题的帖子,希望它有帮助。 https://devforums.apple.com/message/1044432#1044432 – vladof81

+0

@ vladof81,这个作品完美:)。 – sabiland

NSKeyedArchiver将只使用Objective-C类,不是纯粹的斯威夫特类工作。您可以通过使用@objc属性或通过从诸如NSObject之类的Objective-C类继承来将您的类桥接到Objective-C。

更多信息请参见Using Swift with Cocoa and Objective-C

+1

那么,问题就出现了,是否有可能在swift中串行化对象? – tadasz

+0

是的;或者让你的Swift类可用于Obj-C,这样你可以使用NSKeyedArchiver,或者如果你想保持纯Swift,你可以将你自己的序列化成plist或者JSON。 – jatoben

+0

你已经管理它了吗?我也有同样的问题。如果你能发布你的工作源代码,这将是很好的! – Thomas

用的XCode 7.1.1测试,雨燕2.1 &的iOS 9

您有几种选择,以节省您的(数组)自定义对象:

  • NSUserDefaults的:到商店应用设置,首选项,用户默认设置:-)
  • NSKeyedArchiver:用于一般数据存储
  • 核心数据:对于更复杂的数据存储(数据库等)

我离开了这个讨论的核心数据,但想告诉你为什么你应该更好地使用的NSKeyedArchiver NSUserDefaults的上方。

我已经更新您的播放器类,并提供了两种选择方法。虽然上述两种情况,如果比较“负载&保存”方法,你会看到NSKeydArchiver需要更少的代码来处理自定义的数组对象。同样,使用NSKeyedArchiver,您可以轻松地将内容存储到单独的文件中,而不需要担心每个属性的唯一“关键”名称。

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] 
    } 
} 

如上所述如下(单视图的应用程序,在视图控制器的viewDidLoad方法)

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. 
    } 
} 

我测试这个类,这两种方法产生相同的结果

另外在现实生活中,你可以做更好的错误处理,因为归档可能会失败。

在斯威夫特4你不需要NSCoding了!有一个新的协议叫Codable

class Player: Codable { 

    var name = "" 

    init(name: String) { 
     self.name = name 
    } 
} 

而且Codable还支持枚举和结构,所以你可以重写你的播放器类的结构,如果你想!

struct Player: Codable { 
    let name: String 
} 

为了节省您的播放器在Userdefaults:

let player = Player(name: "PlayerOne") 
try? UserDefaults.standard.set(PropertyListEncoder().encode(player), forKey: "player") 

注:PropertyListEncoder()是从框架基金会一类

要检索:

let encoded = UserDefault.standard.object(forKey: "player") as! Data 
let storedPlayer = try! PropertyListDecoder().decode(Player.self, from: encoded) 

欲了解更多信息,阅读https://developer.apple.com/documentation/swift/codable

+0

这是不正确的。仅仅因为你声明一个类Codable不会让它神奇地与NSCoder一起工作。它将与NSCoder一起工作,但你必须对它进行编码和解码,而你的回答并没有提到或解释如何。 – matt

+0

NSCoder在哪里提到?我在谈论Codable替代NSCoding。如果你想知道如何编码/解码,因为它有很多信息,我提供了一个包含更多信息的链接。 –

+0

这是未能理解NSCoder如何参与的问题。您的回答没有显示如何将播放器保存到UserDefaults或NSArchiver。因为你不知道如何?如果你知道,编辑你的答案并展示如何! – matt

我有一个类

class Player { 
     var name = "" 
     init(name: String) { 
      self.name = name 
     } 
    } 

,我想它连载并保存到用户的默认值。

在Swift 4/iOS 11中,有一种全新的方法可以做到这一点。它的优点是,Swift对象可以使用它 - 不仅仅是类,还有结构和枚举。

你会注意到我省略了NSCoding相关的方法,因为你不需要它们来达到这个目的。正如你所知,你在可以这里采用NSCoding;但你不必这样做。 (和一个结构或枚举不能采用NSCoding在所有。)

你开始通过声明你的类如采用可编码协议:

class Player : Codable { 
    var name = "" 
    init(name: String) { 
     self.name = name 
    } 
} 

然后它变成一个简单的事情,它序列化到一个数据对象( NSData)可以存储在UserDefaults中。非常简单的方法是使用属性列表作为中介:

let player = Player(name:"matt") 
try? UserDefaults.standard.set(PropertyListEncoder().encode(player), 
    forKey:"player") 

如果你使用这种方法,现在让我们来证明你可以拉同球员背出UserDefaults的:

if let data = UserDefaults.standard.object(forKey:"player") as? Data { 
    if let p = try? PropertyListDecoder().decode(Player.self, from: data) { 
     print(p.name) // "matt" 
    } 
} 

如果你宁愿传递一个NSKeyedArchiver/NSKeyedUnarchiver,你可以这样做。事实上,在某些情况下,您必须这样做:您将看到一个NSCoder,并且您需要在其中编码您的Codable对象。在最近的测试版中,Xcode 9也引入了一种方法来做到这一点。例如,如果您正在编码,则将NSCoder转换为NSKeyedArchiver并调用encodeEncodable

随着可编码新的IOS 11协议,你现在可以让你的类实现它,它与存档/解除存档对象JSONEncoderJSONDecoder

struct Language: Codable { 
    var name: String 
    var version: Int 
} 

let swift = Language(name: "Swift", version: 4) 
let encoder = JSONEncoder() 
if let encoded = try? encoder.encode(swift) { 
    // save `encoded` somewhere 
} 

if let encoded = try? encoder.encode(swift) { 
if let json = String(data: encoded, encoding: .utf8) { 
    print(json) 
} 

let decoder = JSONDecoder() 
if let decoded = try? decoder.decode(Language.self, from: encoded) { 
    print(decoded.name) 
} 
+0

没有提及作者处理JSON –