Can't decode custom class with NSCoder - ios

I have a User Class that conforms to NSCoder and has properties that are also custom Classes that to conform to NSCoder. It successfully (to my knowledge) encodes when the app closes as seen below.
AppDelegate Class:
func applicationWillTerminate(_ application: UIApplication) {
saveuser()
}
saveuser():
func saveuser() {
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(globUs, toFile: User.archiveURL.path)
if isSuccessfulSave {
print("user saved")
} else {
print("failed save")
}
}
All of that works just fine, but when I start the app up again, I get to this function in my User Class.
required convenience init?(coder aDecoder: NSCoder) {
guard let firstNam = aDecoder.decodeObject(forKey: coderKey.fName) as? String else {
print("trouble decoding first name")
return nil
}
guard let lastNam = aDecoder.decodeObject(forKey: coderKey.lName) as? String else {
print("trouble decoding last name")
return nil
}
guard let bi = aDecoder.decodeObject(forKey: coderKey.bio) as? String else {
print("trouble decoding bio")
return nil
}
guard let tag = aDecoder.decodeObject(forKey: coderKey.tags) as? [Tag] else {
print("trouble decoding tags")
return nil
}
guard let organization = aDecoder.decodeObject(forKey: coderKey.orgs) as? [Organization] else {
print("trouble decoding orgs")
return nil
}
guard let im = aDecoder.decodeObject(forKey: coderKey.orgs) as? UIImage else {
print("trouble decoding img")
return nil
}
self.init(n: firstNam, l: lastNam, b: bi, t: tag, o: organization, i: im)
}
it prints
troubledecoding first name
and exits. This means that aDecoder.decodeObject isn't working. Any ideas as to what I am doing wrong?

Related

How to write set of classes to document directory using NSSecureCoding and NSKeyedArchiver?

I'm having trouble archiving and/or unarchiving (not sure where the problem is, exactly) a set of custom classes from the iOS documents directory. The set is saved to disk (or at least it appears to be saved) because I can pull it from disk but I cannot unarchive it.
The model
final class BlockedUser: NSObject, NSSecureCoding {
static var supportsSecureCoding = true
let userId: String
let name: String
let date: Int
var timeIntervalFormatted: String?
init(userId: String, name: String, date: Int) {
self.userId = userId
self.name = name
self.date = date
}
required convenience init?(coder: NSCoder) {
guard let userId = coder.decodeObject(forKey: "userId") as? String,
let name = coder.decodeObject(forKey: "name") as? String,
let date = coder.decodeObject(forKey: "date") as? Int else {
return nil
}
self.init(userId: userId, name: name, date: date)
}
func encode(with coder: NSCoder) {
coder.encode(userId, forKey: "userId")
coder.encode(name, forKey: "name")
coder.encode(date, forKey: "date")
}
}
Writing to disk
let fm = FileManager.default
let dox = fm.urls(for: .documentDirectory, in: .userDomainMask)[0]
let dir = dox.appendingPathComponent("master.properties", isDirectory: true)
do {
let userData: [URL: Any] = [
/* Everything else in this dictionary is a primitive type (string, bool, etc.)
and reads and writes without problem from disk. The only thing I cannot
get to work is the entry below (the set of custom classes). */
dir.appendingPathComponent("blockedUsers", isDirectory: false): blockedUsers // of type Set<BlockedUser>
]
for entry in userData {
let data = try NSKeyedArchiver.archivedData(withRootObject: entry.value, requiringSecureCoding: true)
try data.write(to: entry.key, options: [.atomic])
}
} catch {
print(error)
}
Reading from disk
if let onDisk = try? Data(contentsOf: dir.appendingPathComponent("blockedUsers", isDirectory: false)) {
if let blockedUsers = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(onDisk) as? Set<BlockedUser> {
print("success")
} else {
print("file found but cannot unarchive") // where I'm currently at
}
} else {
print("file not found")
}
The problem is that you are trying to decode an object instead of decoding an integer. Check this post. Try like this:
class BlockedUser: NSObject, NSSecureCoding {
static var supportsSecureCoding = true
let userId, name: String
let date: Int
var timeIntervalFormatted: String?
init(userId: String, name: String, date: Int) {
self.userId = userId
self.name = name
self.date = date
}
func encode(with coder: NSCoder) {
coder.encode(userId, forKey: "userId")
coder.encode(name, forKey: "name")
coder.encode(date, forKey: "date")
coder.encode(timeIntervalFormatted, forKey: "timeIntervalFormatted")
}
required init?(coder: NSCoder) {
userId = coder.decodeObject(forKey: "userId") as? String ?? ""
name = coder.decodeObject(forKey: "name") as? String ?? ""
date = coder.decodeInteger(forKey: "date")
timeIntervalFormatted = coder.decodeObject(forKey: "timeIntervalFormatted") as? String
}
}

Facing issue while unarchivedObject data

I have created a generic function for NSKeyedArchiver and NSKeyedUnarchiver. I am able to archive the array data but while doing unarchive facing an issue. Below is my code:
NSKeyedArchiver code:
func cacheData<T>(data: T) {
do {
let codedData = try NSKeyedArchiver.archivedData(withRootObject: data, requiringSecureCoding: false)
} catch {
print("Exception while caching data \(error)")
}
}
NSKeyedUnarchiver code:
func getCacheData<T>(encodedData: Data, ofClass: T.Type) -> [T]? {
do{
if let decodedData = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, T.self as! AnyClass], from: encodedData){
return decodedData as? [T]
}
} catch {
print("Exception while decode array cache data \(error)")
}
return nil
}
Above code works fine for having only strings, integers variables but it failed if having custom classes variables. How to allow these custom classes in NSKeyedUnarchiver.
I am getting below error:
Exception while decode array cache data Error
Domain=NSCocoaErrorDomain Code=4864 "value for key 'customclass1' was of
unexpected class 'CustomClass1'. Allowed classes are '{(
NSArray,
MainClass )}'." UserInfo={NSDebugDescription=value for key 'customclass2' was of unexpected class 'CustomClass2'. Allowed classes are
'{(
NSArray,
MainClass )}'.}
Any idea how to solve this?
Make sure all your class are confirming to NSCoding. Something like this:
func archiveAndUnarchive() {
let class2 = Class2(value: "Value")
let class1 = Class1(name: "Name", class2: class2)
do {
// ARCHIVING
let data = try NSKeyedArchiver.archivedData(withRootObject: class1, requiringSecureCoding: false)
// UNARCHIVING
if let decodedData = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Class1 {
print(decodedData)
}
} catch {
print(error)
}
}
class Class1: NSObject, NSCoding {
var name: String?
var class2: Class2?
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "name")
coder.encode(class2, forKey: "class2")
}
required init?(coder: NSCoder) {
super.init()
self.name = coder.decodeObject(forKey: "name") as? String ?? ""
self.class2 = coder.decodeObject(forKey: "class2") as? Class2
}
init(name: String, class2: Class2) {
super.init()
self.name = name
self.class2 = class2
}
}
class Class2: NSObject, NSCoding {
var value: String?
func encode(with coder: NSCoder) {
coder.encode(value, forKey: "value")
}
required init?(coder: NSCoder) {
super.init()
self.value = coder.decodeObject(forKey: "value") as? String
}
init(value: String) {
super.init()
self.value = value
}
}

Error while decoding array of custom objects from NSUserDefaults?

I have an array of the custom object TemplateIndex, which I am trying to save and unsave to NSUserDefaults. But when I decode it, I get the following error:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Here is my custom object:
class TemplateIndex: NSObject, NSCoding {
var identifier: String
var sectionNumber: Int
var indexNumber: Int
init(identifier: String, sectionNumber: Int, indexNumber: Int) {
self.identifier = identifier
self.sectionNumber = sectionNumber
self.indexNumber = indexNumber
}
required init?(coder aDecoder: NSCoder) {
self.identifier = aDecoder.decodeObject(forKey: "identifier") as! String
self.sectionNumber = aDecoder.decodeObject(forKey: "sectionNumber") as! Int
self.indexNumber = aDecoder.decodeObject(forKey: "indexNumber") as! Int
}
func encode(with aCoder: NSCoder) {
aCoder.encode(self.identifier, forKey: "identifier")
aCoder.encode(self.sectionNumber, forKey: "sectionNumber")
aCoder.encode(self.indexNumber, forKey: "indexNumber")
}
}
var favouriteTemplateIdentifiersArray: [TemplateIndex] = []
And here are my save and unsave functions:
func unarchiveFaveTemplates() {
guard let unarchivedObject = UserDefaults.standard.data(forKey: "faveTemplates") else {
return
}
guard let unarchivedFaveTemplates = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(unarchivedObject) else {
return
}
favouriteTemplateIdentifiersArray = unarchivedFaveTemplates as! [TemplateIndex]
print("array opened")
}
func saveFaveTemplates() {
do {
let data = try NSKeyedArchiver.archivedData(withRootObject: favouriteTemplateIdentifiersArray, requiringSecureCoding: false)
UserDefaults.standard.set(data, forKey: "faveTemplates")
UserDefaults.standard.synchronize()
print("array saved")
} catch {
fatalError("can't encode data.")
}
}
Any help is appreciated, thankyou!
EDIT: Working Code
class TemplateIndex: Codable {
var identifier: String
var sectionNumber: Int
var indexNumber: Int
init(identifier: String, sectionNumber: Int, indexNumber: Int) {
self.identifier = identifier
self.sectionNumber = sectionNumber
self.indexNumber = indexNumber
}
}
func unarchiveFaveTemplates() {
if let data = UserDefaults.standard.value(forKey: "faveTemplates") as? Data,
let newArray = try? JSONDecoder().decode(Array<TemplateIndex>.self, from: data) {
print("opened")
favouriteTemplateIdentifiersArray = newArray
}
}
func saveFaveTemplates() {
if let data = try? JSONEncoder().encode(favouriteTemplateIdentifiersArray) {
UserDefaults.standard.set(data, forKey: "faveTemplates")
}
print("changes saved")
}
Forget about NSCoding and NSKeyedArchiver , you need to use Codable
struct TemplateIndex:Codable {
var identifier: String
var sectionNumber,indexNumber: Int
}
guard let data = UserDefaults.standard.data(forKey: "faveTemplates") else {
return
}
do {
let arr = try JSONDecoder().decode([TemplateIndex].self,from:data)
let data = try JSONEncoder().encode(arr)
UserDefaults.standard.set(data, forKey: "faveTemplates")
} catch {
print(error)
}

How to save data in a file swift and load with UserDefault

i already got data from JSON API. so i want to save data profile then i load if i want to use it.
this is my code parse json
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
print(json)
// handle json...
}
DispatchQueue.main.async(
execute:self.LoginDone
)
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
in
print(json)
i will get data profile like Name, Addres and etc. so, i will save this then load
but i want to save it with another file.
Let create a user object, conform NSCoding protocol and implement decode and encode functions, like this:
class User: NSObject, NSCoding {
var name: String!
var address: String!
init(name: String, address: String) {
self.name = name
self.address = address
}
func encode(with aCoder: NSCoder) {
if let name = name {
aCoder.encode(name, forKey: "name")
}
if let address = address {
aCoder.encode(address, forKey: "address")
}
}
required init?(coder aDecoder: NSCoder) {
if aDecoder.containsValue(forKey: "name") {
self.name = aDecoder.decodeObject(forKey: "name") as! String
}
if aDecoder.containsValue(forKey: "address") {
self.address = aDecoder.decodeObject(forKey: "address") as! String
}
}
}
And now, you can save and retrieve a user:
let user = User(name: "Danh", address: "1234 XYZ st")
// save user
let data = NSKeyedArchiver.archivedData(withRootObject: user)
UserDefaults.standard.set(data, forKey: "user")
// retrieve user
if let data = UserDefaults.standard.value(forKey: "user") as? Data,
let user = NSKeyedUnarchiver.unarchiveObject(with: data) as? User {
print("name: \(user.name), address: \(user.address)")
}

Trying to archive an instance of a class conforming to NSCoder

I am working on my first Swift iOS app, having trouble serializing and saving an object whose JSON I fetch from the server. I am using Gloss, a lightweight JSON-parsing library which defines a Decodable protocol through which an instance can be instantiated from JSON. My intention is to load a thing from JSON (a type alias for [String : AnyObject]) by first extracting its id, and then check whether I already have a local archived copy. If I do, unarchive this and get the image. If not, make an asynchronous request for the image file.
The problem is that Thing.localArchiveExists(id) always returns false. Things are successfully instantiated but they always re-fetch the image. I have checked on the file system and confirmed that no archive files are being written. However, I am not seeing "ERROR. Could not archive", which suggests to me that the save succeeded. Am I missing something about how to archive and save NSCoder objects? Thanks!
Here is my implementation of the Decodable protocol:
// MARK: Decodable protocol
// When a thing is loaded from JSON, we load its image from archive if possible.
required init?(json: JSON) {
guard let id: Int = "id" <~~ json else { return nil}
if Thing.localArchiveExists(id) {
guard let savedThing = NSKeyedUnarchiver.unarchiveObjectWithFile(Thing.archiveFilePath(id)) as? Thing else { return nil }
self.id = savedThing.id
self.name = savedThing.name
self.image = savedThing.image
self.imageUrl = savedThing.imageUrl
super.init()
print("Loaded Thing \(self.name) from archive")
}
else {
guard let name: String = "name" <~~ json else { return nil}
guard let imageUrl: NSURL = "url" <~~ json else { return nil}
self.id = id
self.name = name
self.imageUrl = imageUrl
super.init()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let data = NSData(contentsOfURL: imageUrl)
dispatch_async(dispatch_get_main_queue(), {
self.image = UIImage(data: data!)
guard self.save() else {
print("ERROR. Could not archive")
return
}
print("Loaded Thing \(self.name) from server")
})
}
}
}
Here are relevant parts of the Thing class:
// MARK: Properties
var id: Int?
var name: String
var imageUrl: NSURL?
var image: UIImage?
// MARK: Archiving Paths
static let DocumentsDirectory = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
static let ArchiveURL = DocumentsDirectory.URLByAppendingPathComponent("things")
// MARK: Types
struct PropertyKey {
static let nameKey = "name"
static let imageKey = "image"
static let imageUrlKey = "imageUrl"
static let idKey = "id"
}
// Returns the file URL at which a Thing with the given ID should be saved.
class func archiveFilePath(id: Int) -> String {
return Thing.ArchiveURL.URLByAppendingPathComponent("thing\(id)").absoluteString
}
// Checks whether an archived copy of a Thing with the given ID exists.
class func localArchiveExists(id: Int) -> Bool {
let fileManager = NSFileManager.defaultManager()
return fileManager.fileExistsAtPath(Thing.archiveFilePath(id))
}
// MARK: NSCoding
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(name, forKey: PropertyKey.nameKey)
if image != nil {
coder.encodeObject(image!, forKey: PropertyKey.imageKey)
}
if imageUrl != nil {
coder.encodeObject(imageUrl!, forKey: PropertyKey.imageUrlKey)
}
coder.encodeInteger(id!, forKey: PropertyKey.idKey)
}
required convenience init?(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObjectForKey(PropertyKey.nameKey) as! String
let image = aDecoder.decodeObjectForKey(PropertyKey.imageKey) as? UIImage
let imageUrl = aDecoder.decodeObjectForKey(PropertyKey.imageUrlKey) as? NSURL
let id = aDecoder.decodeIntegerForKey(PropertyKey.idKey)
// Must call designated initializer.
self.init(name: name, image: image, imageUrl: imageUrl, id: id)
}
func save() -> Bool {
// For some reason I can't archive to file.
return NSKeyedArchiver.archiveRootObject(self, toFile: Thing.archiveFilePath(self.id!))
}
I figured out my problem: the save failed because I had not yet created the directory in which I was trying to save my Thing.
func save() -> Bool {
let archivedData = NSKeyedArchiver.archivedDataWithRootObject(self)
do {
try NSFileManager.defaultManager().createDirectoryAtURL(Thing.ArchiveURL, withIntermediateDirectories: true, attributes: [:])
try archivedData.writeToFile(Thing.archiveFilePath(self.id!), options: [])
return true
} catch {
print(error)
return false
}
}

Resources