I have a class Person, which is a model
import Foundation
public class Person : NSObject , NSCoding{
public var name:String="";
public var gender:Int = 0;
public var status:Int = 0
override init (){
super.init()
}
public init (name:String, gender:Int){
self.name=name;
self.gender=gender;
}
required public init(coder aDecoder: NSCoder) { }
public func encodeWithCoder(_aCoder: NSCoder) { }
}
Now when I try to retreive [Person] array I use method getPersons(arr : String!) . If there is no data in NSUserDefaults , I create it, put 1 element into it and store using setPersons()method.
The problem is that on retrieving I get an array with correct count (1), but the object is not filled, having "" in String and 0 in Int variables.
private func archivePeople (people : [Person]) -> NSData{
return NSKeyedArchiver.archivedDataWithRootObject(people as NSArray)
}
public func getPersons(arr : String!) -> [Person]{
var array : [Person] = []
if let unarchivedObject = NSUserDefaults.standardUserDefaults().objectForKey(arr) as? NSData {
array = (NSKeyedUnarchiver.unarchiveObjectWithData(unarchivedObject) as? [Person])!
}
if array.count == 0 {
array.append(Person(name: A.DEFAULT_THEIR_NAMES,gender: 0))
setPersons(arr, people: array)
}
return array
}
public func setPersons(key: String, people : [Person]){
let archivedObject = archivePeople(people)
userDefaults.setObject(archivedObject, forKey: key)
userDefaults.synchronize()
}
What is wrong? Do I set or retrieve this in a wrong way?
You need to override properly the NSCoding functions.
It should look like something the code below:
required public convenience init(coder aDecoder: NSCoder) {
let name: String = aDecoder.decodeObject(forKey: "name") as! String
let gender = aDecoder.decodeInt64(forKey: "gender")
let status = aDecoder.decodeInt64(forKey: "status")
self.init(name: name, gender: Int(gender))
self.status = Int(status)
}
public func encodeWithCoder(_aCoder: NSCoder) {
_aCoder.encode(self.name, forKey: "name")
_aCoder.encode(Int64(self.gender), forKey: "gender")
_aCoder.encode(Int64(self.status), forKey: "status")
}
Related
Good day,
I am on the verge of finishing the tutorial app from Code School, and I was really playing around with its archiving and unarchiving the data "to and with a file". So essentially the archiving works, which is the code below.
class func saveOrdersToArchive(cart: Orders) -> Bool {
print(archiveFilePath());
return NSKeyedArchiver.archiveRootObject(cart, toFile: archiveFilePath());
}
The the archiveFilePath() function is implemented this way, it basically creates a file called "cart.archive" and stores it on the simulator's local drive.
class func archiveFilePath() -> String {
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0];
return documentsDirectory.appendingPathComponent("cart.archive").path;
}
So it creates the file correctly then stores the data of type Orders.
But when I try to retrieve the data with my implementation below, it seems that the returned data is marked as "nil".
class func readOrdersFromArchive() -> Orders? {
print(archiveFilePath());
return NSKeyedUnarchiver.unarchiveObject(withFile: archiveFilePath()) as? Orders
}
So, in the main ViewController file, the saving of the Object is implemented below.
//name
productNames = ["1907 Wall set", "1921 Dial phone"];
//cell Images
productImages = [ #imageLiteral(resourceName: "image-cell1"), #imageLiteral(resourceName: "image-cell2")];
//phone Images
phoneImages = [#imageLiteral(resourceName: "phone-fullscreen1"), #imageLiteral(resourceName: "phone-fullscreen2")];
//price
priceProducts = [1.99, 3.99]
oCartProducts = Product(names: productNames, productImages: productImages, cellImages: phoneImages, priceOfProducts: priceProducts);
order = Orders(order_id: 1, orders: oCartProducts);
print(Orders.saveOrdersToArchive(cart: order));
The function prints to true, to indicate successful archive.
The implementation for retrieving the data is implemented below,
if let order1 = Orders.readOrdersFromArchive(){
order = order1
if let o = order.orders{
if let n = o.names{
print(n.count)
}
}
}
The reason I want to print the "count" is to be able to make sure the unwrapped object has values, but the code doesn't go there meaning the object is nil.
I am doing so init in the ViewController before storing the variables as follows,
var oCartProducts = Product(names: [String](), productImages: [UIImage](), cellImages: [UIImage](), priceOfProducts: [Double]());
var order = Orders(order_id: Int(), orders: Product(names: [String](), productImages: [UIImage](), cellImages: [UIImage](), priceOfProducts: [Double]()));
Showing Orders Class,
class Orders : NSObject, NSCoding{
var order_id: Int?
var orders: Product?
init(order_id: Int?, orders: Product?){
self.order_id = order_id;
self.orders = orders;
}
required init?(coder aDecoder: NSCoder) {
self.orders = aDecoder.decodeObject(forKey: "orders") as? Product
self.order_id = aDecoder.decodeInteger(forKey: "order_id")
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.order_id);
aCoder.encode(self.orders);
}
class func archiveFilePath() -> String {
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0];
return documentsDirectory.appendingPathComponent("cart.archive").path;
}
class func readOrdersFromArchive() -> Orders? {
print(archiveFilePath());
return NSKeyedUnarchiver.unarchiveObject(withFile: archiveFilePath()) as? Orders
}
class func saveOrdersToArchive(cart: Orders) -> Bool {
print(archiveFilePath());
return NSKeyedArchiver.archiveRootObject(cart, toFile: archiveFilePath());
}
Showing Product Class,
class Product: NSObject, NSCoding {
var names: [String]?
var productImages: [UIImage]?
var cellImages: [UIImage]?
var priceOfProducts: [Double]?
init(names: [String]?, productImages: [UIImage]?, cellImages: [UIImage]?, priceOfProducts: [Double]?) {
self.names = names;
self.productImages = productImages;
self.cellImages = cellImages;
self.priceOfProducts = priceOfProducts;
}
required init?(coder aDecoder: NSCoder) {
self.names = aDecoder.decodeObject(forKey: "names") as? [String];
self.productImages = aDecoder.decodeObject(forKey: "productNames") as? [UIImage];
self.cellImages = aDecoder.decodeObject(forKey: "cellImages") as? [UIImage];
self.priceOfProducts = aDecoder.decodeObject(forKey: "priceOfProducts") as? [Double];
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.names);
aCoder.encode(self.productImages);
aCoder.encode(self.cellImages);
aCoder.encode(self.priceOfProducts);
}
}
Hope you can shed some light.
Show us your Orders class. Does it conform to NSCoding?
func encode(with aCoder: NSCoder) {
if let id = self.order_id {
aCoder.encode(self.order_id, forKey: "order_id")
}
if let orders = self.orders {
aCoder.encode(self.orders, forKey: "orders")
}
}
When try to encode my custom object in iOS swift get this error from Xcode 8.3
unrecognized selector sent to instance 0x60800166fe80
*** -[NSKeyedArchiver dealloc]: warning: NSKeyedArchiver deallocated without having had -finishEncoding called on it.
And my code like this:
import UIKit
import Foundation
class Place: NSObject {
func setCustomObject(CustomObject obj:Any,Key key:String) {
let encodedObject : Data = NSKeyedArchiver.archivedData(withRootObject: obj)
UserDefaults.standard.set(encodedObject, forKey: key)
}
}
Here's an example how to make an object to conform to NSCoding. Basically you need to provide implementation of two methods - required convenience init?(coder decoder: NSCoder) and encode(with aCoder: NSCoder)
class Book: NSObject, NSCoding {
var title: String?
var pageCount: Int?
// Memberwise initializer
init(title: String,pageCount: Int) {
self.title = title
self.pageCount = pageCount
}
// MARK: NSCoding
// Here you will try to initialize an object from archve using keys you did set in `encode` method.
required convenience init?(coder decoder: NSCoder) {
guard let title = decoder.decodeObject(forKey: "title") as? String else { return nil }
self.init(title: title, pageCount: decoder.decodeInteger(forKey: "pageCount"))
}
// Here you need to set properties to specific keys in archive
func encode(with aCoder: NSCoder) {
aCoder.encode(self.title, forKey: "title")
aCoder.encodeCInt(Int32(self.pageCount), forKey: "pageCount")
}
}
Also I would recommend changing your setCustomObject method to this:
func setCustomObject(obj:NSCoding, key:String) {
let encodedObject : Data = NSKeyedArchiver.archivedData(withRootObject: obj)
UserDefaults.standard.set(encodedObject, forKey: key)
}
This way compiler prevent you passing NSKeyedArchiver an object that does not conform to NSCoding protocol.
If you don't want to provide all properties in the init method you can use default values:
init(title : String? = nil, pageCount: Int? = nil){
self.title = title
self.pageCount = pageCount
}
Now you can just init your object without any properties. Like that Book()
Here is the solutions, you have to implement the two methods
Encode Method
func encode(with aCoder: NSCoder)
Decoding method
required init?(coder aDecoder: NSCoder)
Complete Example code
class User: NSObject , NSCoding
{
var userID : Int = 0
var name : String = ""
var firstName : String = ""
var lastName : String = ""
var username : String = ""
var email : String = ""
override init(){
super.init();
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.userID, forKey: "id");
aCoder.encode(self.firstName, forKey: "first_name");
aCoder.encode(self.lastName, forKey: "last_name");
aCoder.encode(self.name, forKey: "name");
aCoder.encode(self.username,forKey: "username");
aCoder.encode(self.email, forKey: "email");
}
required init?(coder aDecoder: NSCoder) {
super.init()
self.userID = aDecoder.decodeInteger(forKey: "id");
self.firstName = aDecoder.decodeObject(forKey: "first_name") as! String;
self.lastName = aDecoder.decodeObject(forKey: "last_name") as! String;
self.name = aDecoder.decodeObject(forKey: "name") as! String
self.username = aDecoder.decodeObject(forKey: "username") as! String
self.email = aDecoder.decodeObject(forKey: "email") as! String;
}
init(data : [String: AnyObject]) {
super.init()
self.userID = String.numberValue(data["user_id"]).intValue;
self.firstName = String.stringValue(data["first_name"]);
self.lastName = String.stringValue(data["last_name"]);
self.email = String.stringValue(data["email"]);
self.username = String.stringValue(data["user_name"]);
}
class func loadLoggedInUser() -> User {
if let archivedObject = UserDefaults.standard.object(forKey:"CurrentUserAcc"){
if let user = NSKeyedUnarchiver.unarchiveObject(with: (archivedObject as! NSData) as Data) as? User {
return user;
}
}
return User()
}
func saveUser(){
let archivedObject : NSData = NSKeyedArchiver.archivedData(withRootObject: self) as NSData
UserDefaults.standard.set(archivedObject, forKey: "CurrentUserAcc");
UserDefaults.standard.synchronize();
}
func deleteUser(){
UserDefaults.standard.set(nil, forKey: "CurrentUserAcc")
UserDefaults.standard.synchronize();
}
}
This question already has answers here:
Saving array using NSUserDefaults crashes app
(1 answer)
How to store custom objects in NSUserDefaults
(7 answers)
Closed 7 years ago.
I tried several different method that I can to save this class in NSUserDefaults. I don't know how to save class with override function. How can I make it?
class CountryEntity: AnyObject {
private(set) var id: UInt = 0
private(set) var name = ""
override func cityData(data: Dictionary<String, AnyObject>!) {
id = data.uint(key: "id")
name = data.string(key: "name")
}
}
I tried like that but it doesn't help me
private static var _selectedCountryEntity: AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey(countryNameKey) {
didSet {
let savedData = NSKeyedArchiver.archivedDataWithRootObject(selectedCountryEntity as! NSData)
NSUserDefaults.standardUserDefaults().setObject(savedData, forKey: countryNameKey)
NSUserDefaults.standardUserDefaults().synchronize()
}
}
static var selectedCountryEntity: AnyObject? {
get {
return _selectedCountryEntity
}
set {
// if newValue != _selectedCountryTuple {
_selectedCountryEntity = newValue
// }
}
}
To store custom classes in NSUserDefaults, the data type needs to be a subclass of NSObject and should adhere to NSCoding protocol.
1) Create a custom class for your data
class CustomData: NSObject, NSCoding {
let name : String
let url : String
let desc : String
init(tuple : (String,String,String)){
self.name = tuple.0
self.url = tuple.1
self.desc = tuple.2
}
func getName() -> String {
return name
}
func getURL() -> String{
return url
}
func getDescription() -> String {
return desc
}
func getTuple() -> (String,String,String) {
return (self.name,self.url,self.desc)
}
required init(coder aDecoder: NSCoder) {
self.name = aDecoder.decodeObjectForKey("name") as! String
self.url = aDecoder.decodeObjectForKey("url") as! String
self.desc = aDecoder.decodeObjectForKey("desc") as! String
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.name, forKey: "name")
aCoder.encodeObject(self.url, forKey: "url")
aCoder.encodeObject(self.desc, forKey: "desc")
}
}
2) To save data use following function:
func saveData()
{
let data = NSKeyedArchiver.archivedDataWithRootObject(custom)
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(data, forKey:"customArray" )
}
3) To retrieve:
if let data = NSUserDefaults().dataForKey("customArray"),
custom = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? [CustomData] {
// Do something with retrieved data
for item in custom {
print(item)
}
}
Note: Here I am saving and retrieving an array of trhe custom class objects.
I am trying to use NSUserDefaults to save an array in to my app's core data. I thought it would be good to use NSUserDefaults but the problem is that wherever I put the code that creates the default it throws up the SIGABRT error.
Here is the code that creates the default:
let levelArrayDefault = NSUserDefaults.standardUserDefaults()
levelArrayDefault.setValue(levelsArray, forKey: "levelsArray")
levelArrayDefault.synchronize()
levelsArray is an array of List objects:
class List: NSObject, NSCoding {
// MARK: Properties
var name: String
var AnswersArray = [Answer]()
init?(name: String) {
// Initialize stored properties.
self.name = name
if name.isEmpty {
return nil
}
}
required init(coder decoder: NSCoder){
self.AnswersArray = (decoder.decodeObjectForKey("AA") as? [Answer])!
self.name = (decoder.decodeObjectForKey("name") as? String)!
}
func encodeWithCoder(coder: NSCoder) {
if let AnswersArray = AnswersArray { coder.encodeObject(AnswersArray, forKey: "AA") }
if let name = name { coder.encodeObject(name, forKey: "name") }
}
}
class Answer: NSObject, NSCoding {
var EnglishAnswer: String = ""
var ChineseAnswer: String = ""
init(newEng: String, newChi: String){
self.EnglishAnswer = newEng
self.ChineseAnswer = newChi
}
required init(coder decoder: NSCoder){
self.EnglishAnswer = (decoder.decodeObjectForKey("EnglishAnswer") as? String)!
self.ChineseAnswer = (decoder.decodeObjectForKey("ChineseAnswer") as? String)!
}
func encodeWithCoder(coder: NSCoder) {
if let EnglishAnswer = EnglishAnswer { coder.encodeObject(EnglishAnswer, forKey: "EnglishAnswer") }
if let ChineseAnswer = ChineseAnswer { coder.encodeObject(ChineseAnswer, forKey: "ChineseAnswer") }
}
}
How can I stop SIGABRT from popping up and get the array to be stored.
Help would be much appreciated.
You need to convert it to NSData using NSKeyedArchiver before storing it to NSUserDefaults, try like this:
update: Xcode 11.4 • Swift 5.2 or later
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let list = List(name: "Student")
list.answers = [Answer(english: "english answer", chinese: "中文回答")]
let data = (try? NSKeyedArchiver.archivedData(withRootObject: [list], requiringSecureCoding: false)) ?? Data()
UserDefaults.standard.set(data, forKey: "listData")
guard
let loadedData = UserDefaults.standard.data(forKey: "listData"),
let loadedArray = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(loadedData) as? [List]
else { return }
print(loadedData.count)
print(loadedArray.first ?? "none")
print(loadedArray.first?.name ?? "no name")
print(loadedArray.first?.answers.first?.english ?? "no english")
print(loadedArray.first?.answers.first?.chinese ?? "no chinese")
}
}
class Answer: NSObject, NSCoding {
let english: String
let chinese: String
init(english: String, chinese: String) {
self.english = english
self.chinese = chinese
}
required init(coder decoder: NSCoder) {
self.english = decoder.decodeString(forKey: "english")
self.chinese = decoder.decodeString(forKey: "chinese")
}
func encode(with coder: NSCoder) {
coder.encode(english, forKey: "english")
coder.encode(chinese, forKey: "chinese")
}
}
class List: NSObject, NSCoding {
let name: String
fileprivate var data = Data()
var answers: [Answer] {
get {
(try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)) as? [Answer] ?? []
}
set {
data = (try? NSKeyedArchiver.archivedData(withRootObject: newValue, requiringSecureCoding: false)) ?? Data()
}
}
init(name: String) {
self.name = name
}
required init(coder decoder: NSCoder) {
self.data = decoder.decodeData(forKey: "answersData")
self.name = decoder.decodeString(forKey: "name")
}
func encode(with coder: NSCoder) {
coder.encode(data, forKey: "answersData")
coder.encode(name, forKey: "name")
}
}
extension NSCoder {
func decodeString(forKey key: String) -> String {
return decodeObject(forKey: key) as? String ?? ""
}
func decodeData(forKey key: String) -> Data {
return decodeObject(forKey: key) as? Data ?? Data()
}
}
If you want to save your custom object in NSUserDefaults, it's not enough to make your class NSCoding-compliant -- you have to actually encode the data into an NSData object. This is a common mistake -- see my answer to another question for a similar situation.
So, you've added NSCoding to your Answer and List classes. That's a good start. Before you continue, you should verify that you've got that step right by using a NSKeyedArchiver to encode an example of a List object containing a few Answer objects into an instance of NSData, and then use NSKeyedUnarchiver to decode that data object back into your List. Verify that everything that you care about completes the round trip with no problems. This would be an excellent place to use Xcode's testing facility -- you could write a unit test that does exactly what I've described.
Once you know you've got the NSCoding stuff right, you should modify your code so that it encodes your List as NSData and stores the resulting data object in NSUserDefaults using the -setObject:forKey: method.
I try to persiste my object with NSCoding but i always get BAD_ACCESS ERROR To avoid multi multiple like variable, class, i put all common variable in RObject. I think i do something wrong the the init but i don't know what.
the error was thow in this function
func parseInfo(allInfos : String) -> Void {
if let all : JSON = JSON.parse(allInfos) as JSON? {
if let info = all.asArray
{
for description in info
{
var track : RInfo = SCTracks(js: description)
self.arrayTracks.addObject(track)
} // Therad 1: EXC_BAD_ACCESS(code=2, address=0x27...)
}
}
}
The Log doesn't show any thing
My Common Class
class RObject : NSObject, NSCoding {
var id : Int? = 0
var kind : String?
override init() { super.init() }
init(js :JSON) {
self.kind = js["kind"].asString
self.id = js["id"].asInt
super.init()
}
required
init(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeIntegerForKey("id") as Int
self.kind = aDecoder.decodeObjectForKey("kind") as? String
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeInteger(self.id!, forKey: "id")
aCoder.encodeObject(self.kind, forKey: "kind")
}
}
My class Rinfo who inherits from RObject
class RInfo : RObject {
var title :String?
var uri :String?
var license :String?
var release :String?
var user :RUser!
override init() { super.init() }
required init(coder: NSCoder) {
self.title = coder.decodeObjectForKey("title") as? String
self.user = coder.decodeObjectForKey("user") as RUser
self.license = coder.decodeObjectForKey("license") as? String
self.uri = coder.decodeObjectForKey("uri") as? String
self.release = coder.decodeObjectForKey("release") as? String
super.init(coder: coder)
}
init(js :JSON) {
self.user = js(js: js["user"])
self.title = js["title"].asString
self.license = js["license"].asString
self.uri = js["uri"].asString
self.release = js["release"].asString
super.init(js: js)
}
override func encodeWithCoder(encoder: NSCoder) {
encoder.encodeObject(self.title, forKey: "title")
encoder.encodeObject(self.user, forKey: "user")
encoder.encodeObject(self.uri, forKey: "uri")
encoder.encodeObject(self.license, forKey: "license")
}
}
Thanks for any help !
I Solve my problem by remove variable release in RInfo. that strange