Store custom class in UserDefaults - ios

I'm trying to store a custom class in user defaults but throughs an exception
terminating with uncaught exception of type NSException
My class is as follows: Transactions
import Foundation
import SwiftyJSON
class Transactions: NSObject,NSCoding {
var transOBJ = [JSON]()
init(transOBJ:[JSON]) {
self.transOBJ = transOBJ
}
required convenience init(coder aDecoder:NSCoder) {
let obj = aDecoder.decodeObject(forKey: "OBJ") as! [JSON]
self.init(transOBJ: obj)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(transOBJ, forKey: "OBJ")
}
}
The exception is throwing on aCoder.encode(transOBJ, forKey: "OBJ")
The code for saving in user defaults is:
let trans = [Transactions(transOBJ: self.transactionObjs)]
var userDefaults = UserDefaults.standard
let encodedData: Data = NSKeyedArchiver.archivedData(withRootObject: trans)
userDefaults.set(encodedData, forKey: "transactions")
userDefaults.synchronize()
Update 1
I am calling the user defaults inside the Alamofire request function. Then it is giving an error
But when I call it outside Alamofire request function, then it runs just fine. What should I do?
I want to save it inside the success of alamofire but can't do it

Replace this code with your code then remove app and run again.
required init(coder aDecoder: NSCoder) {
transOBJ = = aDecoder.decodeObject(forKey: "OBJ") as! [JSON]
}

Thank you Tj3n. JSON also need to conform NSCoding. Just Transactions isn't enough. I created different variables for string and integers in the Transactions NSObject, extracted data from JSON, stored extracted data in the variables declared and then encoded and decoded them accordingly. It worked.
If there is another way, I'm all ears.

Related

Firebase between different IOS Apps NSKeyedUnarchiver terminating

I am currently working on a Project which contains a Mobile IOS App and a separate iPad App. These communicate with Firebase Database.
On the mobile app it works perfectly to archiver objects and unarchiver it on another mobile device, but on the iPad it I get the following error and a Thread 1: signal SIGABRT.
libc++abi.dylib: terminating with uncaught exception of type NSException
It always terminates in the following line:
let ar = NSKeyedUnarchiver.unarchiveObject(with: array) as! [ProjectModel]
The value array is Data of type Data.
The Object-File:
import Foundation
import UIKit
class ProjectModel: NSObject, NSCoding {
var name: String!
var controllerArray: [ControllerModel]!
init(name: String!, controllerArray: [ControllerModel]!) {
self.name = name
self.controllerArray = controllerArray
}
required convenience init(coder aDecoder: NSCoder) {
let name = aDecoder.decodeObject(forKey: "name") as! String
let controllerArray = aDecoder.decodeObject(forKey: "controllerArray") as! [ControllerModel]
self.init(name: name, controllerArray: controllerArray)
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(controllerArray, forKey: "controllerArray")
}
}
I found a way to solve the Problem. You need to use the Class Codable instead of NSKeyedUnarchiver.
Here a link to a blog post which helps me: medium.com/codable

How to store the custom data like CBPeripheral via nsuserdefaults in swift?

I am developing in Swift. I want to store the custom data via nsuserdefaults.
My custom data is like the following
In ConnectedDevice.swift
import UIKit
import Foundation
import CoreBluetooth
import CoreLocation
class ConnectedDevice : NSObject , NSCoding{
var RSSI_threshold:NSNumber=0
var Current_RSSI:NSNumber=0
var name:String?
var bdAddr:NSUUID?
var ConnectState:Bool=false
var AlertState:Int=0
var BLEPeripheral : CBPeripheral!
var DisconnectAddress:[String] = [String]()
var DisconnectTime:[String] = [String]()
var Battery:NSInteger=0
var Location:[CLLocation] = [CLLocation]()
var AlertStatus:Int!
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(RSSI_threshold, forKey: "RSSI_threshold")
aCoder.encodeObject(Current_RSSI, forKey: "Current_RSSI")
aCoder.encodeObject(name, forKey: "name")
aCoder.encodeObject(bdAddr, forKey: "bdAddr")
aCoder.encodeBool(ConnectState, forKey: "ConnectState")
aCoder.encodeInteger(AlertState, forKey: "AlertState")
aCoder.encodeObject(BLEPeripheral, forKey: "BLEPeripheral")
aCoder.encodeObject(DisconnectAddress, forKey: "DisconnectAddress")
aCoder.encodeObject(DisconnectTime, forKey: "DisconnectTime")
aCoder.encodeObject(Battery, forKey: "Battery")
aCoder.encodeObject(Location, forKey: "Location")
aCoder.encodeObject(AlertStatus, forKey: "AlertStatus")
}
override init() {
}
required init?(coder aDecoder: NSCoder) {
self.RSSI_threshold = aDecoder.decodeObjectForKey("RSSI_threshold") as! NSNumber
self.Current_RSSI = aDecoder.decodeObjectForKey("Current_RSSI") as! NSNumber
self.name = aDecoder.decodeObjectForKey("name") as? String
self.bdAddr = aDecoder.decodeObjectForKey("bdAddr") as? NSUUID
self.ConnectState = aDecoder.decodeObjectForKey("ConnectState") as! Bool
self.AlertState = aDecoder.decodeObjectForKey("AlertState") as! Int
self.BLEPeripheral = aDecoder.decodeObjectForKey("BLEPeripheral") as! CBPeripheral
self.DisconnectAddress = aDecoder.decodeObjectForKey("DisconnectAddress") as! [String]
self.DisconnectTime = aDecoder.decodeObjectForKey("DisconnectTime") as! [String]
self.Battery = aDecoder.decodeObjectForKey("Battery") as! NSInteger
self.Location = aDecoder.decodeObjectForKey("Location") as! [CLLocation]
self.AlertStatus = aDecoder.decodeObjectForKey("AlertStatus") as! Int
}
}
In ViewController.swift
var userDefault = NSUserDefaults.standardUserDefaults()
var BLEConnectedDevice:[ConnectedDevice] = [ConnectedDevice]()
When I try to store the data , I use the following code:
let data = NSKeyedArchiver.archivedDataWithRootObject(BLEConnectedDevice)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: "BLEConnectedDevice")
userDefault.synchronize()
When I try to load the data , I use the following code:
if let Data = NSUserDefaults.standardUserDefaults().objectForKey("BLEConnectedDevice") as? NSData {
if let devcie = NSKeyedUnarchiver.unarchiveObjectWithData(Data) as? ConnectedDevice {
print("NSUserDefaults = \(devcie.name)")
}
}
But it will show the error at aCoder.encodeObject(BLEPeripheral, forKey: "BLEPeripheral") , and the error log is
2016-08-24 18:37:57.022 Anti-Lost[476:222607] -[CBPeripheral encodeWithCoder:]: unrecognized selector sent to instance 0x14e9fc60
2016-08-24 18:37:57.024 Anti-Lost[476:222607] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CBPeripheral encodeWithCoder:]: unrecognized selector sent to instance 0x14e9fc60'
How to encode the CBPeripheral ?
Did I missing something ?
How to store the custom data via nsuserdefaults in swift ?
It's because CBPeripheral do not implement NSCoding. So, you will not be able to archive your BLEPeripheral object into NSUserDefaults.
I think the best solution is to store the properties directly you are interested in (like name or identifier):
aCoder.encodeObject(BLEPeripheral.name, forKey: "BLEPeripheralName")
aCoder.encodeObject(BLEPeripheral.identifier, forKey: "BLEPeripheralIdentifier")
Edit:
I think you will not be able to store and build again your CBPeripheral, no matter how. Apple presents the CBPeripheral object like that:
So, that seems very logical that you cannot store this object and build it back later ... I think the best solution is to store the BLEPeripheral.identifier and fetch back the CBPeripheral later using the CBCentralManager. You can do it by using the retrievePeripherals(withIdentifiers:) method. Here is the documentation. And I think you should never build a CBPeripheral object by yourself.

NSKeyedArchiver Custom Object Array

NOTE: Xcode 8 Beta 6
I am not sure what I am missing but for some reason I am getting the following error on the NSKeyedArchiver.archivedData line.
'NSInvalidArgumentException', reason: '-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x60000024c690'
*** First throw call stack:
Here is my class which conforms to the NSCoding protocol:
enum PhraseType {
case create
case item
}
class Phrase: NSObject, NSCoding {
var englishPhrase :String!
var translatedPhrase :String!
var phraseType :PhraseType! = PhraseType.item
required init?(coder decoder: NSCoder) {
self.englishPhrase = decoder.decodeObject(forKey: "englishPhrase") as! String
self.translatedPhrase = decoder.decodeObject(forKey: "translatedPhrase") as! String
self.phraseType = decoder.decodeObject(forKey: "phraseType") as! PhraseType
}
func encode(with coder: NSCoder) {
coder.encode(self.englishPhrase, forKey: "englishPhrase")
coder.encode(self.translatedPhrase, forKey: "translatedPhrase")
coder.encode(self.phraseType, forKey: "phraseType")
}
init(englishPhrase :String, translatedPhrase :String) {
self.englishPhrase = englishPhrase
self.translatedPhrase = translatedPhrase
super.init()
}
}
And here is the code for the archiving:
let userDefaults = UserDefaults.standard
var phrases = userDefaults.object(forKey: "phrases") as? [Phrase]
if phrases == nil {
phrases = [Phrase]()
}
phrases?.append(phrase)
let phrasesData = NSKeyedArchiver.archivedData(withRootObject: phrases)
userDefaults.set(phrasesData, forKey: "phrases")
userDefaults.synchronize()
Any ideas?
You can't encode a Swift enum such as PhraseType. Instead, give this enum a raw value and encode the raw value (and on decode, use that to reconstruct the correct enum case).
When you've done that, you'll need to cast your Swift array to NSArray to get it archived properly.

Cannot retrieve data from NSUserDefaults into today extension (Swift)

I have a group of arrays that all contain a number of instances of a custom Task object that I have created. I am saving the arrays to NSUserDefaults as follows:
Custom Task Object
class Task:NSObject, NSCoding {
var name:String
var notes:String
var date:NSDate
var dateUse:Bool
var taskCompleted:Bool
init(name:String, notes:String, date:NSDate, dateUse:Bool, taskCompleted:Bool){
self.name = name
self.notes = notes
self.date = date
self.dateUse = dateUse
self.taskCompleted = taskCompleted
}
required init(coder decoder: NSCoder){
self.name = decoder.decodeObjectForKey("name") as! String
self.notes = decoder.decodeObjectForKey("notes") as! String
self.date = decoder.decodeObjectForKey("date") as! NSDate
self.dateUse = decoder.decodeObjectForKey("dateUse") as! Bool
self.taskCompleted = decoder.decodeObjectForKey("taskCompleted") as! Bool
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeObject(self.notes, forKey: "notes")
coder.encodeObject(self.date, forKey: "date")
coder.encodeObject(self.dateUse, forKey: "dateUse")
coder.encodeObject(self.taskCompleted, forKey: "taskCompleted")
}
}
Saving:
let defaults = NSUserDefaults(suiteName: "group.com.myGroupName")
let nowData = NSKeyedArchiver.archivedDataWithRootObject(nowTasks)
defaults!.setObject(nowData, forKey: "nowData")
Retrieving
let nowDataPull = defaults!.objectForKey("nowData") as? NSData
if let nowDataPull2 = nowDataPull{
let nowTasks2 = NSKeyedUnarchiver.unarchiveObjectWithData(nowDataPull2) as? [Task]
if let nowTasks21 = nowTasks2{
nowTasks = nowTasks21
}
}
The above method works fine for setting and retrieving data from the iPhone itself. However, this method does not work when trying to retrieve the data via the today extension.
When trying to retrieve from the today extension's .swift file I get the following errors:
Failed to inherit CoreMedia permissions from 52550: (null)
Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '* -[NSKeyedUnarchiver
decodeObjectForKey:]: cannot decode object of class (MyAppName.Task)
for key (NS.objects); the class may be defined in source code or a
library that is not linked'
I know that the extension can read the data because when I call:
if (defaults?.objectForKey("nowData") != nil){
print("there is data")
}
I get the printed response..
I can successfully save an Integer and retrieve via the today extension, but not objectForKey
I have tried various other saving methods including .plist files, but nothing seems to work. The same errors keep occurring. Any input would be greatly appreciated!
another way is to fix the name of the class used for NSCoding. You simply have to use:
NSKeyedArchiver.setClassName("Task", forClass: Task.self before serializing
NSKeyedUnarchiver.setClass(Task.self, forClassName: "Task") before deserializing
wherever needed.
Looks like iOS extensions prefix the class name with the extension's name.
Set Object
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject("iOS", forKey: "userNameKey")
defaults.setInteger(25, forKey: "Age")
defaults.setBool(true, forKey: "UseTouchID")
defaults.setDouble(M_PI, forKey: "Pi")
Reading
let defaults = NSUserDefaults.standardUserDefaults()
let name = defaults.stringForKey("userNameKey")
Workaround:
So I have found a workaround (which is actually more efficient) for my problem. Because the decoding of [Task] was causing problems, I decided to go ahead and retrieve the data from an array that did not have objects inside of it, but rather just NSDate() instances. Because of this I can now save and access the important aspects of data that the today extension needs. I urge anyone else who is having issues with custom class decoding to try and simplify your data if at all possible in order to retrieve and use it in your extension. I am not exactly sure what the problem was or is, but retrieving simple data from an array stored in NSUserDefaults works without problems.
Hope all of this nonsense can help someone else out there!

NSInvalidUnarchiveOperationException raised only in iOS extension, not main app

I have archived an Array of my NSCoding-conforming class "Assignment" to a file in a shared (App Groups) container:
class private func updateSharedFiles() {
let path = NSFileManager().containerURLForSecurityApplicationGroupIdentifier("group.agenda-touch")!.path! + "/widgetData"
if let incompleteAssignmentsArray = self.incompleteAssignments {
NSKeyedArchiver.archiveRootObject(incompleteAssignmentsArray, toFile: path)
}
}
Now when my extension wants to read from that file I called NSFileManager.fileExistsAtPath: on the file and it returns true. Finally I call NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? AssignmentArray
and get an NSInvalidUnarchiveOperationException. The thing is, when I unarchive the file in my main app, I get no such exception. And to be clear, I HAVE added Assignment.swift to my compile sources for the widget. So my TodayViewController knows what Assignment is, but can't decode it for some reason.
As an addendum, here is the NSCoding implementation for Assignment:
//MARK: NSCoding
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.assignmentName, forKey: "assignmentName")
aCoder.encodeObject(self.assignmentDueDate, forKey: "assignmentDueDate")
aCoder.encodeObject(self.assignmentSubject, forKey: "assignmentSubject")
aCoder.encodeBool(self.completed, forKey: "assignmentCompleted")
aCoder.encodeObject(self.creationDate, forKey: "assignmentCreationDate")
aCoder.encodeInteger(self.assignmentType.rawValue, forKey: "assignmentType")
aCoder.encodeBool(self.earmarkedForDeletion, forKey: "assignmentEarmarked")
aCoder.encodeObject(self.modificationDates, forKey: "assignmentModificationDates")
}
required convenience init(coder aDecoder: NSCoder) {
let assignmentName = aDecoder.decodeObjectForKey("assignmentName") as String
let assignmentDueDate = aDecoder.decodeObjectForKey("assignmentDueDate") as NSDate
let assignmentSubject = aDecoder.decodeObjectForKey("assignmentSubject") as String
let completed = aDecoder.decodeBoolForKey("assignmentCompleted")
self.init(name:assignmentName,dueDate:assignmentDueDate,subject:assignmentSubject,completed:completed)
self.creationDate = aDecoder.decodeObjectForKey("assignmentCreationDate") as NSDate
let assignmentTypeRaw = aDecoder.decodeIntegerForKey("assignmentType")
self.assignmentType = AssignmentType(rawValue: assignmentTypeRaw)!
self.earmarkedForDeletion = aDecoder.decodeBoolForKey("assignmentEarmarked")
self.modificationDates = aDecoder.decodeObjectForKey("assignmentModificationDates") as Dictionary<String,NSDate>
}
I have the exact same problem. I tried to remove and add the file to and from compile sources. Not working either. Have you found a solution yet?
Only difference with my problem: I try to unarchive an NSArray of objects xxx. It works, when archiving and unarchiving in the main app OR archiving and unarchiving in the WatchKit app, but not when I archive it in the main app and unarchive it in the WatchKit app (and otherwise).
let defaults: NSUserDefaults = NSUserDefaults(suiteName: "group.name")!
if let object: NSData = defaults.objectForKey(ArrayKey) as? NSData {
if let array: AnyObject = NSKeyedUnarchiver.unarchiveObjectWithData(object) {
return array as NSArray
}
}
return NSArray()
The exchange of objects through NSUserDefaults within my app group works. I only experience this problem with the NSKeyedArchiver and NSKeyedUnarchiver of an NSArray with objects xxx.
* Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '* -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class xxx
EDIT: I resolved the issue in making my object NSSecureCoding compliant. I therefore replaced
propertyName = aDecoder.decodeObjectForKey("propertyName")
with
propertyName = aDecoder.decodeObjectOfClass(NSString.self, forKey: "propertyName") as NSString
and added
class func supportsSecureCoding() -> Bool {
return true
}
I also set the className of NSKeyedUnarchiver und NSKeyedArchiver in applicationFinishLaunchingWithOptions of the main app and in the first WKInterfaceController in the WatchKitApp
NSKeyedArchiver.setClassName("Song", forClass: Song.self)
NSKeyedUnarchiver.setClass(Song.self, forClassName: "Song")
I also had the exact same problem.
To sum up from previous answers and comments, the solution lies the setClass/setClassName statements. I wrap my encoding as functions in the example code below (Swift 5):
import Foundation
public class User: NSObject, NSSecureCoding
{
public var id: String
public var name: String
public init(id: String, name: String)
{
self.id = id
self.name = name
}
// NSSecureCoding
public static var supportsSecureCoding: Bool { return true }
public func encode(with coder: NSCoder)
{
coder.encode(id , forKey: "id")
coder.encode(name, forKey: "name")
}
public required init?(coder: NSCoder)
{
guard
let id = coder.decodeObject(forKey: "id") as? String,
let name = coder.decodeObject(forKey: "name") as? String
else {
return nil
}
self.id = id
self.name = name
}
// (Un)archiving
public static func decode(from data: Data) -> Self?
{
NSKeyedUnarchiver.setClass(Self.self, forClassName: String(describing: Self.self))
return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Self
}
public func encode() -> Data?
{
NSKeyedArchiver.setClassName(String(describing: Self.self), for: Self.self)
return try? NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: Self.supportsSecureCoding)
}
}

Resources