[NSKeyedUnarchiver init]: cannot use -init for initialization' error when trying to save a custom class - swift

I get the error:
[NSKeyedUnarchiver init]: cannot use -init for initialization
when trying to load a custom class.
Heres my Init for the class:
required init?(coder aDecoder: NSCoder) {
print("intializing")
if let quoteName = aDecoder.decodeObjectForKey("quoteName") as? String {
self._quoteName = quoteName
}
if let quote = aDecoder.decodeObjectForKey("quote") as? String {
self._quote = quote
}
if let soundFileName = aDecoder.decodeObjectForKey("soundFileName") as? String {
self._soundFileName = soundFileName
}
if let soundFileType = aDecoder.decodeObjectForKey("soundFileType") as? String {
self._soundFileType = soundFileType
}
if let audioFilePath = aDecoder.decodeObjectForKey("audioFilePath") as? String {
self._soundFileType = audioFilePath
}
if let state = aDecoder.decodeObjectForKey("state") as? Bool {
self._state = state
}
if let number = aDecoder.decodeObjectForKey("number") as? Int {
self._number = number
}
}
Heres my Encoder function
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self._quoteName, forKey: "quoteName")
aCoder.encodeObject(self._quote, forKey: "quote")
aCoder.encodeObject(self._soundFileType, forKey: "soundFileName")
aCoder.encodeObject(self._soundFileType, forKey: "soundFileType")
aCoder.encodeObject(self._audioFilePath, forKey: "audioFilePath")
aCoder.encodeObject(self._state, forKey: "state")
aCoder.encodeObject(self._number, forKey: "number")
}
Any finally my call to load:
class func loadQuoteList(){
print("loading Quotes")
quoteList.removeAll()
var quoteListLength = defaults.integerForKey("quoteListLength")
//unarchives Quote Object
var unarc = NSKeyedUnarchiver()
if let data = defaults.objectForKey("Quote") as? NSData {
unarc = NSKeyedUnarchiver(forReadingWithData: data)
}
print("loading individual quotes")
for(var index = 0; index < quoteListLength; index++){
var newQuote = unarc.decodeObjectForKey("Quote\(index)") as! Quote
quoteList[index] = newQuote
print(newQuote._quoteName)
}
}

Related

How to save array of objects (with image variables) in Swift and Xcode?

I am wondering how to save an array of objects from the following class:
class CustomDocument: NSObject, NSCoding {
let name : String
let image : UIImage
init(n: String, i: UIImage){
name = n
image = i
}
//other code excluded
}
Originally, I saved this array to User Defaults. Because the objects took up a lot of space, it caused a lot of lag in the app.
What is the best way to save an array of data that takes up a lot of space?
Thank you so much for the help and all responses are appreciated.
Try this code, Hope it helps:
class CustomDocument: NSObject, NSCoding {
var name : String?
var image : UIImage?
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "namekey")
if let imageData = image!.jpegData(compressionQuality: 1.0){
aCoder.encode(imageData, forKey: "imagekey")
}
UserDefaults.standard.synchronize()
}
required convenience init?(coder aDecoder: NSCoder) {
self.init()
if let name = (aDecoder.decodeObject(forKey: "namekey") as? String){
self.name = name
}
if let imageData = (aDecoder.decodeObject(forKey: "imagekey") as? Data){
if let image = UIImage(data: imageData){
self.image = image
}
}
}
}
func archiveDocument(document:CustomDocument) -> Data? {
do {
let archivedObject = try NSKeyedArchiver.archivedData(withRootObject: document, requiringSecureCoding: false)
return archivedObject
} catch {
// do something with the error
}
return nil
}
func unarchiveDocument(unarchivedObject:Data) -> CustomDocument? {
do {
if let document = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(unarchivedObject) as? CustomDocument {
return document
}
} catch {
// do something with the error
}
return nil
}
Example:
//Set the object, also you can use an array instead of an object
let obj = CustomDocument()
obj.name = "doc1"
obj.image = UIImage(named: "my_image")
if let archivedObject = archiveDocument(document: obj){
UserDefaults.standard.set(archivedObject, forKey: "obj")
}
//Get the object
if let archivedObject = UserDefaults.standard.data(forKey: "obj"){
obj = unarchiveDocument(unarchivedObject: archivedObject)
let myImage = obj?.image
}

Swift 4 Tuple Iteration

I am trying to retrieve wallpapers from Bing by json in Swift 4
https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=8&mkt=en-US
But images property is a tuple with another objects. I can not iterate this tuple and reach the url values so that I can collect them in an array.
(
{
bot = 1;
copyright = "For the winter solstice, Santa Fe's Farolito Walk (\U00a9 Julien McRoberts/Danita Delimont)";
copyrightlink = "http://www.bing.com/search?q=santa+fe+new+mexico&form=hpcapt&filters=HpDate:%2220181221_0800%22";
drk = 1;
enddate = 20181222;
fullstartdate = 201812210800;
hs = (
);
hsh = 6ba85f1fbab1b9a290a19af763ca404d;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181221_AdobeSantaFe%22&FORM=HPQUIZ";
startdate = 20181221;
title = "All is bright in Santa Fe";
top = 1;
url = "/az/hprichbg/rb/AdobeSantaFe_EN-US4037753534_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/AdobeSantaFe_EN-US4037753534";
wp = 1;
},
{
bot = 1;
copyright = "Nabana-no-Sato gardens at Nagashima Spa Land in Kuwana, Japan (\U00a9 Julian Krakowiak/Alamy)";
copyrightlink = "http://www.bing.com/search?q=nabana+no+sato+nagashima+spa+land&form=hpcapt&filters=HpDate:%2220181220_0800%22";
drk = 1;
enddate = 20181221;
fullstartdate = 201812200800;
hs = (
);
hsh = eda366ca6eee5c653a59d8f16a54ae63;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181220_WinterIllumination%22&FORM=HPQUIZ";
startdate = 20181220;
title = "Winter illuminations in Nabana-no-Sato";
top = 1;
url = "/az/hprichbg/rb/WinterIllumination_EN-US0071328313_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/WinterIllumination_EN-US0071328313";
wp = 0;
},
{
bot = 1;
copyright = "The Charles Bridge in Prague, Czech Republic (\U00a9 borchee/E+/Getty Images)";
copyrightlink = "http://www.bing.com/search?q=prague&form=hpcapt&filters=HpDate:%2220181219_0800%22";
drk = 1;
enddate = 20181220;
fullstartdate = 201812190800;
hs = (
);
hsh = 37c19c3f45952fd81c429e5e92c386e9;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181219_PragueChristmas%22&FORM=HPQUIZ";
startdate = 20181219;
title = "Snow falls on Bohemia";
top = 1;
url = "/az/hprichbg/rb/PragueChristmas_EN-US8649790921_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/PragueChristmas_EN-US8649790921";
wp = 1;
},
{
bot = 1;
copyright = "For the anniversary of the premiere of 'The Nutcracker,' a scene of the Moscow Ballet performing the popular dance (\U00a9 Tytus Zmijewski/Epa/Shutterstock)";
copyrightlink = "http://www.bing.com/search?q=the+nutcracker&form=hpcapt&filters=HpDate:%2220181218_0800%22";
drk = 1;
enddate = 20181219;
fullstartdate = 201812180800;
hs = (
);
hsh = b6303e82458de5692e94b26cb332fbd1;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181218_NutcrackerSeason%22&FORM=HPQUIZ";
startdate = 20181218;
title = "A holiday tradition is born";
top = 1;
url = "/az/hprichbg/rb/NutcrackerSeason_EN-US8373379424_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/NutcrackerSeason_EN-US8373379424";
wp = 0;
},
{
bot = 1;
copyright = "Wilbur Wright gliding down Big Kill Devil Hill in Kitty Hawk, North Carolina (\U00a9 Library of Congress)";
copyrightlink = "http://www.bing.com/search?q=wright+brothers+first+flight&form=hpcapt&filters=HpDate:%2220181217_0800%22";
drk = 1;
enddate = 20181218;
fullstartdate = 201812170800;
hs = (
);
hsh = 5a132d06c5e7b66f0a1ec9f434a0dca1;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181217_WrightGlider%22&FORM=HPQUIZ";
startdate = 20181217;
title = "Wright brothers fly into history";
top = 1;
url = "/az/hprichbg/rb/WrightGlider_EN-US10185286591_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/WrightGlider_EN-US10185286591";
wp = 0;
},
{
bot = 1;
copyright = "Holiday decorations on a canal in Murano, Italy (\U00a9 John Warburton-Lee/DanitaDelimont.com)";
copyrightlink = "http://www.bing.com/search?q=murano+island&form=hpcapt&filters=HpDate:%2220181216_0800%22";
drk = 1;
enddate = 20181217;
fullstartdate = 201812160800;
hs = (
);
hsh = c4cc26f7a803502632d940f9466e2b7c;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181216_MuranoChristmas%22&FORM=HPQUIZ";
startdate = 20181216;
title = "Murano aglow";
top = 1;
url = "/az/hprichbg/rb/MuranoChristmas_EN-US10759540271_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/MuranoChristmas_EN-US10759540271";
wp = 1;
},
{
bot = 1;
copyright = "The Stoneman Bridge on the Merced River in Yosemite National Park (\U00a9 Ron_Thomas/E+/Getty Images)";
copyrightlink = "http://www.bing.com/search?q=yosemite+national+park&form=hpcapt&filters=HpDate:%2220181215_0800%22";
drk = 1;
enddate = 20181216;
fullstartdate = 201812150800;
hs = (
);
hsh = 6a49f8fc62b09ce83305ac0a13000a7a;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181215_YosemiteBridge%22&FORM=HPQUIZ";
startdate = 20181215;
title = "Season of solitude in Yosemite";
top = 1;
url = "/az/hprichbg/rb/YosemiteBridge_EN-US10544416282_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/YosemiteBridge_EN-US10544416282";
wp = 1;
},
{
bot = 1;
copyright = "A female northern cardinal (\U00a9 Matthew Studebaker/Minden Pictures)";
copyrightlink = "http://www.bing.com/search?q=northern+cardinal&form=hpcapt&filters=HpDate:%2220181214_0800%22";
drk = 1;
enddate = 20181215;
fullstartdate = 201812140800;
hs = (
);
hsh = 0dcf20ffcfd5413167161ad20b412fb5;
quiz = "/search?q=Bing+homepage+quiz&filters=WQOskey:%22HPQuiz_20181214_CardinalBerries%22&FORM=HPQUIZ";
startdate = 20181214;
title = "The Christmas Bird Count begins";
top = 1;
url = "/az/hprichbg/rb/CardinalBerries_EN-US11262203078_1920x1080.jpg";
urlbase = "/az/hprichbg/rb/CardinalBerries_EN-US11262203078";
wp = 1;
}
)
How can I handle this JSON tuple in Swift 4?
That's not a tuple.
That's how a NSArray is printed (that's the description implementation result). It's more Objective-C like.
(
content...
)
With force unwrap (use of !) that I'd strongly recommend to not use in production, and use if let/guard let instead (because it will cause a crash if it fails the unwrap). I used force unwrap to be more direct, that's all, show the logic.
let dataJSON = initialJSONData
//Your JSON is a Dictionary at top level
let json = try! JSONSerialization.jsonObject(with: dataJSON, options: []) as! [String: Any]
//The value of a images key is an Array of Dictionaries
let imagesArray = json["images"] as! [[String: Any]]
let imagesURLStr = imagesArray.flatMap{ $0["url"] as? String }
Now, if you use Swift 4+, I'd recommend to use Codable:
struct Result: Codable {
let images: [Image]
struct Image: Codable {
let url: String
}
}
To call it:
let jsonDecoder = JSONDecoder()
let result = try! jsonDecoder.decode(Result.self, from: dataJSON)
let images = result.images
let imagesURLStr = images.map{ $0.url }
Side Note:
This code is not tested, written only here, so there might be a typo error, a minor compiler issue but it shouldn't be un-fixable.
With the help of quicktype.io, let's use these structs :
struct Result: Codable {
let images: [Image]
let tooltips: Tooltips
}
struct Image: Codable {
let startdate, fullstartdate, enddate, url: String
let urlbase, copyright: String
let copyrightlink: String
let title, quiz: String
let wp: Bool
let hsh: String
let drk, top, bot: Int
let hs: [JSONAny]
}
struct Tooltips: Codable {
let loading, previous, next, walle: String
let walls: String
}
// MARK: Encode/decode helpers
class JSONNull: Codable, Hashable {
public static func == (lhs: JSONNull, rhs: JSONNull) -> Bool {
return true
}
public var hashValue: Int {
return 0
}
public init() {}
public required init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if !container.decodeNil() {
throw DecodingError.typeMismatch(JSONNull.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for JSONNull"))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encodeNil()
}
}
class JSONCodingKey: CodingKey {
let key: String
required init?(intValue: Int) {
return nil
}
required init?(stringValue: String) {
key = stringValue
}
var intValue: Int? {
return nil
}
var stringValue: String {
return key
}
}
class JSONAny: Codable {
let value: Any
static func decodingError(forCodingPath codingPath: [CodingKey]) -> DecodingError {
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Cannot decode JSONAny")
return DecodingError.typeMismatch(JSONAny.self, context)
}
static func encodingError(forValue value: Any, codingPath: [CodingKey]) -> EncodingError {
let context = EncodingError.Context(codingPath: codingPath, debugDescription: "Cannot encode JSONAny")
return EncodingError.invalidValue(value, context)
}
static func decode(from container: SingleValueDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if container.decodeNil() {
return JSONNull()
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout UnkeyedDecodingContainer) throws -> Any {
if let value = try? container.decode(Bool.self) {
return value
}
if let value = try? container.decode(Int64.self) {
return value
}
if let value = try? container.decode(Double.self) {
return value
}
if let value = try? container.decode(String.self) {
return value
}
if let value = try? container.decodeNil() {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer() {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decode(from container: inout KeyedDecodingContainer<JSONCodingKey>, forKey key: JSONCodingKey) throws -> Any {
if let value = try? container.decode(Bool.self, forKey: key) {
return value
}
if let value = try? container.decode(Int64.self, forKey: key) {
return value
}
if let value = try? container.decode(Double.self, forKey: key) {
return value
}
if let value = try? container.decode(String.self, forKey: key) {
return value
}
if let value = try? container.decodeNil(forKey: key) {
if value {
return JSONNull()
}
}
if var container = try? container.nestedUnkeyedContainer(forKey: key) {
return try decodeArray(from: &container)
}
if var container = try? container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key) {
return try decodeDictionary(from: &container)
}
throw decodingError(forCodingPath: container.codingPath)
}
static func decodeArray(from container: inout UnkeyedDecodingContainer) throws -> [Any] {
var arr: [Any] = []
while !container.isAtEnd {
let value = try decode(from: &container)
arr.append(value)
}
return arr
}
static func decodeDictionary(from container: inout KeyedDecodingContainer<JSONCodingKey>) throws -> [String: Any] {
var dict = [String: Any]()
for key in container.allKeys {
let value = try decode(from: &container, forKey: key)
dict[key.stringValue] = value
}
return dict
}
static func encode(to container: inout UnkeyedEncodingContainer, array: [Any]) throws {
for value in array {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer()
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: JSONCodingKey.self)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout KeyedEncodingContainer<JSONCodingKey>, dictionary: [String: Any]) throws {
for (key, value) in dictionary {
let key = JSONCodingKey(stringValue: key)!
if let value = value as? Bool {
try container.encode(value, forKey: key)
} else if let value = value as? Int64 {
try container.encode(value, forKey: key)
} else if let value = value as? Double {
try container.encode(value, forKey: key)
} else if let value = value as? String {
try container.encode(value, forKey: key)
} else if value is JSONNull {
try container.encodeNil(forKey: key)
} else if let value = value as? [Any] {
var container = container.nestedUnkeyedContainer(forKey: key)
try encode(to: &container, array: value)
} else if let value = value as? [String: Any] {
var container = container.nestedContainer(keyedBy: JSONCodingKey.self, forKey: key)
try encode(to: &container, dictionary: value)
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
}
static func encode(to container: inout SingleValueEncodingContainer, value: Any) throws {
if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? Int64 {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? String {
try container.encode(value)
} else if value is JSONNull {
try container.encodeNil()
} else {
throw encodingError(forValue: value, codingPath: container.codingPath)
}
}
public required init(from decoder: Decoder) throws {
if var arrayContainer = try? decoder.unkeyedContainer() {
self.value = try JSONAny.decodeArray(from: &arrayContainer)
} else if var container = try? decoder.container(keyedBy: JSONCodingKey.self) {
self.value = try JSONAny.decodeDictionary(from: &container)
} else {
let container = try decoder.singleValueContainer()
self.value = try JSONAny.decode(from: container)
}
}
public func encode(to encoder: Encoder) throws {
if let arr = self.value as? [Any] {
var container = encoder.unkeyedContainer()
try JSONAny.encode(to: &container, array: arr)
} else if let dict = self.value as? [String: Any] {
var container = encoder.container(keyedBy: JSONCodingKey.self)
try JSONAny.encode(to: &container, dictionary: dict)
} else {
var container = encoder.singleValueContainer()
try JSONAny.encode(to: &container, value: self.value)
}
}
}
We can get the url properties this way:
//Create the URL
guard let url = URL(string: "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=8&mkt=en-US") else {
fatalError("Invalid URL")
}
//Fetch the data from the URL
URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) -> Void in
guard let jsonData = data, error == nil else {
fatalError("Data error")
}
let jsonDecoder = JSONDecoder()
do {
let result = try jsonDecoder.decode(Result.self, from: jsonData)
let images = result.images
let urls = images.map { $0.url }
urls.forEach { print($0) }
}
catch {
print(error)
}
}).resume()
This prints:
/az/hprichbg/rb/AdobeSantaFe_EN-US4037753534_1920x1080.jpg
/az/hprichbg/rb/WinterIllumination_EN-US0071328313_1920x1080.jpg
/az/hprichbg/rb/PragueChristmas_EN-US8649790921_1920x1080.jpg
/az/hprichbg/rb/NutcrackerSeason_EN-US8373379424_1920x1080.jpg
/az/hprichbg/rb/WrightGlider_EN-US10185286591_1920x1080.jpg
/az/hprichbg/rb/MuranoChristmas_EN-US10759540271_1920x1080.jpg
/az/hprichbg/rb/YosemiteBridge_EN-US10544416282_1920x1080.jpg
/az/hprichbg/rb/CardinalBerries_EN-US11262203078_1920x1080.jpg

NSKeyedUnarchiver can't decode some of my keys

I have some keys I want to decode, but it only decodes my first string and then it gives error.
class Profile: NSCoder, NSCoding {
struct Constants {
// Age
static let minimumAge: Int = 5
static let maximumAge: Int = 120
static let defaultAge: Int = 25
// Gender
static let defaultGender: Int = 0
// Daily meals
static let defaultDailyMeals: Int = 3
static let minimumDailyMeals: Int = 1
static let maximumDailyMeals: Int = 10
// Do you train
static let defaultIsTraining: Bool = false
}
//MARK: Properties
private var name: String
private var age: Int
private var gender: Int
private var target: Int
private var dailyMeals: Int
private var isTraining: Bool
private var trainingWhen: Int?
private var trainingDays: [(Int)];
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("profile")
//MARK: Types
struct PropertyKey {
static let name = "profileName"
static let age = "profileAge"
static let gender = "profileGender"
static let target = "profileTarget"
static let dailyMeals = "profileDailyMeals"
static let isTraining = "profileIsTraining"
static let trainingWhen = "profileTrainingWhen"
static let trainingDays = "trainingDays"
}
// Main init
init?(name: String, age: Int, gender: Int, target: Int, dailyMeals: Int, isTraining: Bool, trainingWhen: Int?, trainingDays: [(Int)]) {
guard !name.isEmpty else {
print("Name empty")
return nil
}
guard age > Constants.minimumAge && age < Constants.maximumAge else {
print("Age not in range")
return nil
}
self.name = name
self.age = age
self.gender = gender
self.target = target
self.dailyMeals = dailyMeals
self.isTraining = isTraining
self.trainingWhen = trainingWhen
self.trainingDays = trainingDays
}
//MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(self.name, forKey: PropertyKey.name)
aCoder.encode(self.age, forKey: PropertyKey.age)
aCoder.encode(self.gender, forKey: PropertyKey.gender)
aCoder.encode(self.target, forKey: PropertyKey.target)
aCoder.encode(self.dailyMeals, forKey: PropertyKey.dailyMeals)
aCoder.encode(self.isTraining, forKey: PropertyKey.isTraining)
aCoder.encode(self.trainingWhen, forKey: PropertyKey.trainingWhen)
aCoder.encode(self.trainingDays, forKey: PropertyKey.trainingDays)
}
func dump_data(){
print("Name: " + self.name)
print("Age: " + String(self.age))
print("Gender: " + String(self.gender))
print("Target: " + String(self.target))
print("Daily Meals: " + String(self.dailyMeals))
print("Does train?: " + String(self.isTraining))
print("When: " + String(describing: self.trainingWhen))
print("Days: " + String(describing: self.trainingDays))
}
required convenience init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: PropertyKey.name) as? String else {
print("Unable to decode the name for a Profile object.")
return nil
}
print("Decoded: Name - \(name)")
guard let age = aDecoder.decodeObject(forKey: PropertyKey.age) as? Int else {
print("Unable to decode the age for a Profile object.")
return nil
}
guard let gender = aDecoder.decodeObject(forKey: PropertyKey.gender) as? Int else {
print("Unable to decode the gender for a Profile object.")
return nil
}
guard let target = aDecoder.decodeObject(forKey: PropertyKey.target) as? Int else {
print("Unable to decode the target for a Profile object.")
return nil
}
guard let dailyMeals = aDecoder.decodeObject(forKey: PropertyKey.dailyMeals) as? Int else {
print("Unable to decode the daily meals for a Profile object.")
return nil
}
guard let isTraining = aDecoder.decodeObject(forKey: PropertyKey.isTraining) as? Bool else {
print("Unable to decode the training state for a Profile object.")
return nil
}
let trainingWhen: Int? = aDecoder.decodeObject(forKey: PropertyKey.trainingWhen) as? Int ?? nil
let trainingDays: [(Int)] = aDecoder.decodeObject(forKey: PropertyKey.trainingDays) as? [(Int)] ?? []
// Must call designated initializer.
self.init(name: name, age: age, gender: gender, target: target, dailyMeals: dailyMeals, isTraining: isTraining, trainingWhen: trainingWhen, trainingDays: trainingDays)
}
The error message I get is
Unable to decode the age for a Profile object.
As long as I know this means that my aDecoder.decodeObject(forKey: PropertyKey.gender) as? Int returns nil and I have completely no idea why this happens, because I encode it as you can see in the code. :/
You should probably use the decodeInteger(forKey:_) method, like this:
self.age = aDecoder.decodeInteger(forKey: PropertyKey.age)
With that said, what I usually do in these cases (considering you only need one instance of the class) is this:
//: Playground - noun: a place where people can play
import UIKit
class Profile: NSCoder, NSCoding {
struct Constants {
// Age
static let minimumAge: Int = 5
static let maximumAge: Int = 120
static let defaultAge: Int = 25
// Gender
static let defaultGender: Int = 0
// Daily meals
static let defaultDailyMeals: Int = 3
static let minimumDailyMeals: Int = 1
static let maximumDailyMeals: Int = 10
// Do you train
static let defaultIsTraining: Bool = false
}
//MARK: Properties
var name: String = ""
var age: Int = 0
var gender: Int = 0
var target: Int = 0
var dailyMeals: Int = 0
var isTraining: Bool = false
var trainingWhen: Int? = nil
var trainingDays = [Int]();
//MARK: Archiving Paths
static let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].path
static let fileName = "profile"
static let path = "\(Profile.documentsDirectory)/\(fileName)"
//MARK: Types
struct PropertyKey {
static let name = "profileName"
static let age = "profileAge"
static let gender = "profileGender"
static let target = "profileTarget"
static let dailyMeals = "profileDailyMeals"
static let isTraining = "profileIsTraining"
static let trainingWhen = "profileTrainingWhen"
static let trainingDays = "trainingDays"
}
// Main init
static var shared = Profile()
fileprivate override init() { }
//MARK: NSCoding
func encode(with archiver: NSCoder) {
archiver.encode(self.name, forKey: PropertyKey.name)
archiver.encode(self.age, forKey: PropertyKey.age)
archiver.encode(self.gender, forKey: PropertyKey.gender)
archiver.encode(self.target, forKey: PropertyKey.target)
archiver.encode(self.dailyMeals, forKey: PropertyKey.dailyMeals)
archiver.encode(self.isTraining, forKey: PropertyKey.isTraining)
archiver.encode(self.trainingWhen, forKey: PropertyKey.trainingWhen)
archiver.encode(self.trainingDays, forKey: PropertyKey.trainingDays)
}
override var description: String {
return """
Name: \(self.name)
Age: \(self.age)
Gender: \(self.gender)
Target: \(self.target)
Daily Meals: \(self.dailyMeals)
Does train?: \(self.isTraining)
When: \(self.trainingWhen ?? 0)
Days: \(self.trainingDays)
"""
}
func save() -> Bool {
return NSKeyedArchiver.archiveRootObject(self, toFile: Profile.path)
}
required init(coder unarchiver: NSCoder) {
super.init()
if let name = unarchiver.decodeObject(forKey: PropertyKey.name) as? String {
self.name = name
}
self.age = unarchiver.decodeInteger(forKey: PropertyKey.age)
self.gender = unarchiver.decodeInteger(forKey: PropertyKey.gender)
self.target = unarchiver.decodeInteger(forKey: PropertyKey.target)
self.dailyMeals = unarchiver.decodeInteger(forKey: PropertyKey.dailyMeals)
self.isTraining = unarchiver.decodeBool(forKey: PropertyKey.isTraining)
if let trainingWhen = unarchiver.decodeObject(forKey: PropertyKey.trainingWhen) as? Int {
self.trainingWhen = trainingWhen
}
if let trainingDays = unarchiver.decodeObject(forKey: PropertyKey.trainingDays) as? [Int] {
self.trainingDays = trainingDays
}
}
}
// TEST:
Profile.shared.name = "Daniel"
Profile.shared.age = 10
Profile.shared.gender = 30
Profile.shared.target = 10
Profile.shared.dailyMeals = 90
Profile.shared.isTraining = true
Profile.shared.trainingWhen = 20
Profile.shared.trainingDays = [1,2,5]
Profile.shared.save()
if let profile = NSKeyedUnarchiver.unarchiveObject(withFile: Profile.path) as? Profile {
print(profile.description)
}
/*
Output:
--------
Name: Daniel
Age: 10
Gender: 30
Target: 10
Daily Meals: 90
Does train?: true
When: 20
Days: [1, 2, 5]
*/

Why is retrieval from NSUserDefaults failing for my custom object?

I have a class PredicPair which inherits from NSCoding and NSObject as such:
class PredicPair: NSObject, NSCoding {
var weight : Float
var prediction : String
init(weight: Float, prediction: String) {
self.weight = weight
self.prediction = prediction
super.init()
}
func encode(with aCoder: NSCoder) {
aCoder.encode(weight, forKey: "weight")
aCoder.encode(prediction, forKey: "prediction")
}
required convenience init(coder aDecoder: NSCoder) {
let unarchivedWeight = aDecoder.decodeObject(forKey: "weight") as! Float
let unarchivedPrediction = aDecoder.decodeObject(forKey: "prediction") as! String
self.init(weight: unarchivedWeight, prediction: unarchivedPrediction)
}
class func saveToUserDefaults(pairs: [PredicPair]) {
let dataBlob = NSKeyedArchiver.archivedData(withRootObject: pairs)
UserDefaults.standard.set(dataBlob, forKey: "test")
UserDefaults.standard.synchronize()
}
class func loadFromUserDefaults() -> [PredicPair]? {
guard let decodedNSDataBlob = UserDefaults.standard.object(forKey: "test") as? NSData,
let loadedFromUserDefault = NSKeyedUnarchiver.unarchiveObject(with: decodedNSDataBlob as Data) as? [PredicPair]
else {
return nil
}
return loadedFromUserDefault
}
}
I am trying to store an array of the class in UserDefaults and retrieving it, but the latter always returns nil. Any reason why?
let predicPair1 = PredicPair(weight: 0, prediction: "there")
let predicPair2 = PredicPair(weight: 1, prediction: "hello")
let array = [predicPair1, predicPair2]
PredicPair.saveToUserDefaults(pairs: array)
if let retreivedArray = PredicPair.loadFromUserDefaults() {
print("loaded \(retreivedArray.count) players from NSUserDefaults")
} else {
print("failed")
}
I've also tried saving to a file using NSKeyedArchiver but retrieval fails as well.

Swift, unexpectedly found nil while unwrapping an Optional value (NSKeyedUnarchiver)

I want to store an Array in caching by below way.
But somehow I am getting the error "unexpectedly found nil while unwrapping an Optional value"
My Code Below
Load from Cache
func loadCachedContacts(){
let loadNSCachedContacts = NSUserDefaults.standardUserDefaults().objectForKey(constants.defaultsKeys.VISITED_CONTACTS_KEY) as? NSData
if let genericsData = loadNSCachedContacts {
if let conArray = NSKeyedUnarchiver.unarchiveObjectWithData(genericsData) as?
[Generic] //**error comes here**
{
//if(conArray != nil){
// if let testArray = conArray {
for generic in conArray {
print("\(generic.genericCode), Cached Storage")
}
}
//}
}
}
Save into Caching
func storeVisitedContact(generic: Generic)
{
let def = NSUserDefaults.standardUserDefaults()
var readArray : [Generic] = []
// visitedContactsArr.append(generic)
if var testArray = def.objectForKey(constants.defaultsKeys.VISITED_CONTACTS_KEY) as? [Generic] {
// readArray = testArray! as! [Generic]
if testArray.count>=constants.defaultsKeys.VISITED_CONTACTS_COUNT
{
testArray.removeFirst()
}
else{
testArray.append(generic)
}
readArray = testArray
}
else{
readArray.append(generic)
}
let archivedObject = NSKeyedArchiver.archivedDataWithRootObject(readArray as [Generic])
NSUserDefaults.standardUserDefaults().setObject(archivedObject, forKey: constants.defaultsKeys.VISITED_CONTACTS_KEY)
NSUserDefaults.standardUserDefaults().synchronize()
}
Can someone please help me with some solutions?
Editing with NSCoding protocol
class Generic: NSObject, NSCoding
{
var genericCode = String("")
var genericName = String("")
var genericType = String("")
var genericImageUrl = String("")
var genericPhone = String("")
var orgName = String("")
var const = Constants()
override init()
{
}
//Parameterzed Constructor for the Generic
init(genericCode: String , genericName: String , genericPhone: String, genericType: String, genericImageUrl : String)
{
self.genericCode = genericCode
self.genericName = genericName
self.genericType = genericType
self.genericImageUrl = genericImageUrl
self.genericPhone = genericPhone
}
required init(coder aDecoder: NSCoder) {
genericCode = aDecoder.decodeObjectForKey("genericCode") as! String
genericName = aDecoder.decodeObjectForKey("genericName") as! String
genericType = aDecoder.decodeObjectForKey("genericType") as! String
genericPhone = aDecoder.decodeObjectForKey("genericPhone") as! String
orgName = aDecoder.decodeObjectForKey("orgName") as! String
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(genericCode, forKey: "genericCode")
aCoder.encodeObject(genericName, forKey: "genericName")
aCoder.encodeObject(genericType, forKey: "genericType")
aCoder.encodeObject(genericPhone, forKey: "genericPhone")
}
}
It looks like you are not encoding any value for key "orgName" in func encodeWithCoder(aCoder: NSCoder)
When the init(coder aDecoder: NSCoder) is run, it will try to decode a value for key "orgName" which does not exist, since it has been omitted in encodeWithCoder(aCoder: NSCoder). The decoding of this key will result in nil which can not be unwrapped.