How to unarchive object using NSKeyedUnarchiver? - swift

**I'm using this class: **
class Person: NSObject, NSCoding {
var name: String
var image: String
init(name: String, image: String) {
self.name = name
self.image = image
}
required init(coder aDecoder: NSCoder){
name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
image = aDecoder.decodeObject(forKey: "image") as? String ?? ""
}
func encode(with coder: NSCoder) {
coder.encode(name, forKey: "name")
coder.encode(image, forKey: "image")
}
}
For archiving, i used this method:
if let savedData = try? NSKeyedArchiver.archivedData(withRootObject: people, requireSecureCoding: false)
whrere people is Person class array [Person]
As for unarchiving, this method:
NSKeyedUnarchiver.unarchivedTopLevelObjectWithData()
is deprecated... which method should i use now ?

You now must tell the system what type you expect rather than simply unarchiving whatever is found:
try NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClass: Person.self, from: savedData)

Related

How to save a CapturedRoom using NSCoder

I'm trying to build an app that creates a floor plan of a room. I used ARWorldMap with ARPlaneAnchors for this but I recently discovered the Beta version of the RoomPlan API, which seems to lead to far better results.
However, I used te be able to just save an ARWorldMap using the NSCoding protocol, but this throws an error when I try to encode a CapturedRoom object:
-[__SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x141c18110
My code for encoding the class containing the CapturedRoom:
import RoomPlan
class RoomPlanScan: NSObject, NSCoding {
var capturedRoom: CapturedRoom
var title: String
var notes: String
init(capturedRoom: CapturedRoom, title: String, notes: String) {
self.capturedRoom = capturedRoom
self.title = title
self.notes = notes
}
required convenience init?(coder: NSCoder) {
guard let capturedRoom = coder.decodeObject(forKey: "capturedRoom") as? CapturedRoom,
let title = coder.decodeObject(forKey: "title") as? String,
let notes = coder.decodeObject(forKey: "notes") as? String
else { return nil }
self.init(
capturedRoom: capturedRoom,
title: title,
notes: notes
)
}
func encode(with coder: NSCoder) {
coder.encode(capturedRoom, forKey: "capturedRoom")
coder.encode(title, forKey: "title")
coder.encode(notes, forKey: "notes")
}
}
To be clear, the following code does work:
import RoomPlan
class RoomPlanScan: NSObject, NSCoding {
var worldMap: ARWorldMap
var title: String
var notes: String
init(worldMap: ARWorldMap, title: String, notes: String) {
self.worldMap = worldMap
self.title = title
self.notes = notes
}
required convenience init?(coder: NSCoder) {
guard let capturedRoom = coder.decodeObject(forKey: "worldMap") as? ARWorldMap,
let title = coder.decodeObject(forKey: "title") as? String,
let notes = coder.decodeObject(forKey: "notes") as? String
else { return nil }
self.init(
worldMap: worldMap,
title: title,
notes: notes
)
}
func encode(with coder: NSCoder) {
coder.encode(worldMap, forKey: "worldMap")
coder.encode(title, forKey: "title")
coder.encode(notes, forKey: "notes")
}
}
I'm writing the object to a local file using NSKeyedArchiver so it would be nice if I could keep the same structure using NSCoder. How can I fix this and save a CapturedRoom?
The issue is about saving CaptureRoom. According to the doc, it's not NS(Secure)Coding compliant, but it conforms to Decodable, Encodable, and Sendable
So you can use an Encoder/Decoder, to do CaptureRoom <-> Data, you could use the bridge NSData/Data, since NSData is NS(Secure)Coding compliant.
So, it could be something like the following code. I'll use JSONEncoder/JSONDecoder as partial Encoder/Decoder because they are quite common.
Encoding:
let capturedRoomData = try! JSONEncoder().encode(capturedRoom) as NSData
coder.encode(capturedRoomData, forKey: "capturedRoom")
Decoding:
let captureRoomData = coder.decodeObject(forKey: "capturedRoom") as! Data
let captureRoom = try! JSONDecoder().decode(CaptureRoom.self, data: captureRoomData)
Side note:
I used force unwrap (use of !) to simplify the code logic, but of course, you can use do/try/catch, guard let, if let, etc.)

Save and get details of Model class using userdefault

I have one model object User and inside that another model object is Picture.
"user": {
"id": 1,
"email": "abc.k#gmail.com",
"user_profile_photo": {
"id": 997,
"user_id": 1,
"photo_url": "https://newproduction.s3.amazonaws.com/profile_image/RkUJAczv5nWpUyFTgyTgMLChR.jpeg",
}
}
I have two model class for this one is User and another is Picture inside user.
I am saving model user in userdefault as below
//Get Response
loginResponseObj = Mapper<LoginResponse>().map(JSONObject:(response.result.value))
//Save user Details
let userData = loginResponseObj.user!
let data = NSKeyedArchiver.archivedData(withRootObject: userData)
UserDefaults.standard.set(data, forKey:"user")
and when i am trying to get data from userdefaults, am getting User Model but inside details are nil.
Get Userdetails from userdefault Code is below
guard let data = UserDefaults.standard.object(forKey: "user") as? Data
else
{
return UserModel()
}
return (NSKeyedUnarchiver.unarchiveObject(with: data) as? UserModel)!
This return **<ABC.User: 0x7f84f740c440>**
But when i am trying to get Picture from User it return nil
In User Model
class User:NSObject,Mappable,NSCoding{
var email: String?
var picture: Picture?
required init?(coder aDecoder: NSCoder) {
self.email = aDecoder.decodeObject(forKey: "email") as? String
}
func initWithCoder(aDecoder:NSCoder) -> UserModel
{
self.email = aDecoder.decodeObject(forKey: "email") as? String
return self
}
func encode(with aCoder: NSCoder) {
aCoder.encode(email, forKey: "email")
}
}
In Picture Model
class Picture:Mappable,NSCoding{
var id: String?
var photoURL: String?
required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeObject(forKey: "id") as? String
self.photoURL = aDecoder.decodeObject(forKey: "photoURL") as? String
}
func initWithCoder(aDecoder:NSCoder) -> Picture
{
self.id = aDecoder.decodeObject(forKey: "id") as? String
self.photoURL = aDecoder.decodeObject(forKey: "photoURL") as? String
return self
}
func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
}
}
Note: I am using MVVM Pattern and Object Mapper
So, How can i get the whole details of user including photo_url (User.Picture.photo_url) ?
In User Model
class User:NSObject,Mappable,NSCoding{
var email: String?
var picture: Picture?
required init?(coder aDecoder: NSCoder) {
self.email = aDecoder.decodeObject(forKey: "email") as? String
self.picture = aDecoder.decodeObject(forKey: "picture") as? Picture
}
func initWithCoder(aDecoder:NSCoder) -> UserModel
{
self.email = aDecoder.decodeObject(forKey: "email") as? String
self.picture = aDecoder.decodeObject(forKey: "picture") as? Picture
return self
}
func encode(with aCoder: NSCoder) {
aCoder.encode(email, forKey: "email")
aCoder.encode(picture, forKey: "picture")
}
}
In Picture Model
class Picture: NSObject,Mappable,NSCoding {
var id: String?
var photoURL: String?
required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeObject(forKey: "id") as? String
self.photoURL = aDecoder.decodeObject(forKey: "photoURL") as? String
}
func initWithCoder(aDecoder:NSCoder) -> Picture
{
self.id = aDecoder.decodeObject(forKey: "id") as? String
self.photoURL = aDecoder.decodeObject(forKey: "photoURL") as? String
return self
}
func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: "id")
aCoder.encode(photoURL, forKey: "photoURL")
}
Get Details From User Defaults
guard let data = UserDefaults.standard.object(forKey: "user") as? Data
else
{
return User()
}
return (NSKeyedUnarchiver.unarchiveObject(with: data) as? User)!
above code will return User Model
Using this model access its variables
print(user.email)
print(user.picture.photoURL)

Getting optional String from singleton

I've created a class with some vars and lets. One of these vars is a String. I store them in UserDefaults. If I want to access the string of this class over a singleton class, I will always get an optional String. I don't know why.
Here is the class of the object:
import Foundation
import SpriteKit
class BallSkinsClass: NSObject, NSCoding {
let id: Int
var name: String
var isBuyed: Bool
let ID = "id"
let NAME = "name"
let ISBUYED = "isBuyed"
init(id: Int, name: String, isBuyed: Bool) {
self.id = id
self.name = name
self.isBuyed = isBuyed
}
required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeInteger(forKey: ID)
self.name = String(describing: aDecoder.decodeObject(forKey: NAME))
self.isBuyed = aDecoder.decodeBool(forKey: ISBUYED)
}
#objc func encode(with aCoder: NSCoder) {
aCoder.encode(id, forKey: ID)
aCoder.encode(name, forKey: NAME)
aCoder.encode(isBuyed, forKey: ISBUYED)
}
}
To declare the skins, access, save and load I have these functions in my BallSkinsClass:
import Foundation
import SpriteKit
import GameKit
class BallSkins {
static var sharedInstance = BallSkins()
private init() {
}
let BALLSKINS = "ballSkins"
var standard: BallSkinsClass! = BallSkinsClass(id: 0, name: "Standard", isBuyed: true)
var billiard: BallSkinsClass! = BallSkinsClass(id: 1, name: "Billard", isBuyed: false)
var emoji: BallSkinsClass! = BallSkinsClass(id: 2, name: "Emojis", isBuyed: false)
func archiveBallSkins(ballSkins:[BallSkinsClass]) -> NSData {
print("archiving Skins")
let archivedBallSkins = NSKeyedArchiver.archivedData(withRootObject: ballSkins as Array)
return archivedBallSkins as NSData
}
func saveBallSkins(ballSkins:[BallSkinsClass]) {
let archivedBallSkins = archiveBallSkins(ballSkins: ballSkins)
UserDefaults.standard.set(archivedBallSkins, forKey: BALLSKINS)
print("saving Skins")
}
func retrieveBallSkins() -> [BallSkinsClass]? {
print("retrieving Skins")
if let unarchivedBallSkins = UserDefaults.standard.object(forKey: BALLSKINS) as? NSData {
return NSKeyedUnarchiver.unarchiveObject(with: unarchivedBallSkins as Data) as? [BallSkinsClass]
}
return nil
}
func loadBallSkins() {
print("loading Skins")
let archivedBallSkins = retrieveBallSkins()
for ballSkin in archivedBallSkins! {
switch ballSkin.id {
case 0 :
standard.isBuyed = ballSkin.isBuyed
case 1:
billiard.isBuyed = ballSkin.isBuyed
case 2:
emoji.isBuyed = ballSkin.isBuyed
default:
standard.isBuyed = ballSkin.isBuyed
}
}
}
}
If I want to access the name of the skin in any other scene or view I call:
ballSkins.sharedInstance.billiard.name
But this is an optional every time! I don't know why or where the error is.
I suppose it is caused by
required init?(coder aDecoder: NSCoder) {
self.id = aDecoder.decodeInteger(forKey: ID)
self.name = String(describing: aDecoder.decodeObject(forKey: NAME))
self.isBuyed = aDecoder.decodeBool(forKey: ISBUYED)
}
3rd line generates optional string because according to documentation
func decodeObject() -> Any?
and String(describing: ...) does not unwrap your value. You must unwrap all values from UserDefaults by yourself, providing defaultValue if nil is not possible

How do you save a custom class as an attribute of a CoreData entity in Swift 3?

I have a CoreData Entity SavedWorkout. It has the following attributes:
completionCounter is an array of Bool, and workout is a custom class called Workout.
I am saving my data like so:
let saveCompletionCounter = currentCompletionCounter
let saveDate = Date() as NSDate
let saveRoutineIndex = Int16(currentWorkoutRoutine)
let saveWorkout = NSKeyedArchiver.archivedData(withRootObject: workout)
item.setValue(saveDate, forKey: "date")
item.setValue(saveWorkout, forKey: "workout")
item.setValue(saveRoutineIndex, forKey: "routineIndex")
item.setValue(saveCompletionCounter, forKey: "completionCounter")
do {
try moc.save()
print("save successful")
} catch {
print("saving error")
}
where moc is an instance of NSManagedObjectContext, and item is an instance of NSManagedObject:
moc = appDelegate.managedObjectContext
entity = NSEntityDescription.entity(forEntityName: "SavedWorkout", in: moc)!
item = NSManagedObject(entity: entity, insertInto: moc)
In accordance with this and this and this , I have made my Workout class conform to NSObject and NSCoding, so it now looks like this:
class Workout: NSObject, NSCoding {
let name: String
let imageName: String
let routine: [WorkoutRoutine]
let shortDescription: String
required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: "name") as! String
imageName = aDecoder.decodeObject(forKey: "imageName") as! String
routine = aDecoder.decodeObject(forKey: "routine") as! [WorkoutRoutine]
shortDescription = aDecoder.decodeObject(forKey: "shortDescription") as! String
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(imageName, forKey: "imageName")
aCoder.encode(routine, forKey: "routine")
aCoder.encode(shortDescription, forKey: "shortDescription")
}
init(name: String, imageName: String, routine: [WorkoutRoutine], shortDescription: String) {
self.name = name
self.imageName = imageName
self.routine = routine
self.shortDescription = shortDescription
}
}
However I always get an error on the line routine: aDecoder.decodeObject....
The error says:
NSForwarding: warning: object 0x60800002cbe0 of class 'App.WorkoutRoutine' does not implement methodSignatureForSelector: -- trouble ahead
Unrecognized selector -[FitLift.WorkoutRoutine replacementObjectForKeyedArchiver:]
Why does this give me an error and not the other Transformable attribute? How do I save a custom class as a property of a CoreData entity?
The issue is that WorkoutRoutine is itself a custom class and as of your error it is not NSCoding compliant, therefore aCoder.encode(routine, forKey: "routine") doesn't really know how to encode it, as well as routine = aDecoder.decodeObject(forKey: "routine") as! [WorkoutRoutine] doesn't know how to decode it.
Not really related, but please try a safer approach for your coder and encoder initializer as the force unwrap might cause crashes if the encoder does not contain the keys you are looking for (for any reason)
required init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: "name") as? String,
let imageName = aDecoder.decodeObject(forKey: "imageName") as? String,
let routine = aDecoder.decodeObject(forKey: "routine") as? [WorkoutRoutine],
let shortDescription = aDecoder.decodeObject(forKey: "shortDescription") as? String else {
return nil
}
self.name = name
self.imageName = imageName
self.routine = routine
self.shortDescription = shortDescription
}

Swift3: [String: AnyObject] using NSCoding protocol to save with NSUserDefaults

I have my custom class:
private class PendingRequest : NSObject, NSCoding
{
var route : String!
var params: [String: AnyObject]!
init(route: String, params: [String: AnyObject])
{
self.route = route
self.params = params
}
required init(coder aDecoder: NSCoder)
{
self.route = aDecoder.decodeObject(forKey: "route") as! String
self.params = aDecoder.decodeObject(forKey: "params") as! [String: AnyObject]
}
public func encode(with coder: NSCoder)
{
coder.encode(route, forKey: "route")
coder.encode(params, forKey: "params")
}
}
And here is how I save my list of PendingRequest:
private static func savePendingRequests(requestsToSend: [PendingRequest])
{
let data = NSKeyedArchiver.archivedData(withRootObject: requestsToSend!)
UserDefaults.standard.set(data, forKey: self.requestsToSendDefaultsString)
}
And here is how I try to retrieve it:
let data = UserDefaults.standard.data(forKey: requestsToSendDefaultsString)
let requests = NSKeyedUnarchiver.unarchiveObject(with: data!) as! [PendingRequest]
return requests
But my code crashes when retrieving my data... Saving works fine, but not retrieving... Any idea?
EDIT: Here is my Crashlytics error:
*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (_TtCC6Versus9VersusAPIP33_8D2B8AA415FDC4F8DFAF29D7ECE33C1F14PendingRequest) for key (NS.objects); the class may be defined in source code or a library that is not linked
SOLUTION:
My class PendingRequest was fileprivate, thus NSKeyedUnarchiver couldn't access it, hence the crash... Thanks for your help.
Few things are incorrect
you're forgetting to call super.init() in your initializers
init(route: String, params: [String: AnyObject])
{
self.route = route
self.params = params
super.init()
}
required init(coder aDecoder: NSCoder)
{
self.route = aDecoder.decodeObject(forKey: "route") as? String
self.params = aDecoder.decodeObject(forKey: "params") as? [String: AnyObject]
// Check if route and params are what you expected here
super.init()
}
You have a static method that calls "self", I'm not sure this works, you could still have a static key or a global key.
Plus you're unwrapping a non-optional value "requestsToSend".
I think this would solve it.
private static func savePendingRequests(requestsToSend:[PendingRequest])
{
let data = NSKeyedArchiver.archivedData(withRootObject: requestsToSend)
UserDefaults.standard.set(data, forKey: "AStaticKey")
}
Then retrieving should work. (I added a few safety checks)
guard let data = UserDefaults.standard.data(forKey: "AStaticKey"),
let requests = NSKeyedUnarchiver.unarchiveObject(with: data) as? [PendingRequest] else {
return [PendingRequest]()
}
return requests