Search code examples
iosarraysswiftswift2nsuserdefaults

Using NSUserDefaults on arrays


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.


Solution

  • 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()
        }
    }