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.
Related
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.
I have enum like this:
enum Direction : String {
case EAST = "east"
case SOUTH = "south"
case WEST = "west"
case NORTH = "north"
}
And I have a variable called result which is a Hashmap using these enum direction as the key.
var result = [Direction:[String]]()
I tried to encode this object and send to the other side through the multipeer framework. However, it fails at the encoder.
aCoder.encode(self.result, forKey: "result")
Error says that:
"encode(with aCoder: NSCoder)
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x17045f230'
How can I encode this Hashmap?
Thanks.
As noted in JAL's comment NSCoding feature is based on Objective-C runtime, so you need to convert your Dictionary to something which is safely convertible to NSDictionary.
For example:
func encode(with aCoder: NSCoder) {
var nsResult: [String: [String]] = [:]
for (key, value) in result {
nsResult[key.rawValue] = value
}
aCoder.encode(nsResult, forKey: "result")
//...
}
required init?(coder aDecoder: NSCoder) {
let nsResult = aDecoder.decodeObject(forKey: "result") as! [String: [String]]
self.result = [:]
for (nsKey, value) in nsResult {
self.result[Direction(rawValue: nsKey)!] = value
}
//...
}
this Struct is work in swift 2
I have a Swift 3 struct like this.
let tempContacts = NSMutableArray()
let arrayOfArray = NSMutableArray()
I have encode The Person Object in this for loop
for person in tempContacts as! [Person] {
let encodedObject: Data = NSKeyedArchiver.archivedData(withRootObject: person) as Data
arrayOfArray.add(encodedObject)
}
I have decode the data in this for loop
let tempContacts2 = NSMutableArray()
for data in arrayOfArray {
let person: Person = NSKeyedUnarchiver.unarchiveObject(with: data as! Data) as! Person
tempContacts2.add(person)
}
but unarchiveObject is always return nil value
First your model class should conform to the NSCoder protocol. The rest is really simple, there's no need to store the archived results for each object in an array, you can pass the initial array directly to NSKeyedArchiver like this :
class Person: NSObject, NSCoding {
var name = ""
init(name: String) {
self.name = name
}
// NSCoder
required convenience init?(coder decoder: NSCoder) {
guard let name = decoder.decodeObject(forKey: "name") as? String else { return nil }
self.init(name: name)
}
func encode(with coder: NSCoder) {
coder.encode(self.name, forKey: "name")
}
}
let tempContacts = [Person(name: "John"), Person(name: "Mary")]
let encodedObjects = NSKeyedArchiver.archivedData(withRootObject: tempContacts)
let decodedObjects = NSKeyedUnarchiver.unarchiveObject(with: encodedObjects)
As a side note : if NSCoder compliance is correctly implemented in your model class, you can of course use your way of archiving/unarchiving individual objects too. So your original code works too, with some minor adjustments:
for person in tempContacts {
let encodedObject = NSKeyedArchiver.archivedData(withRootObject: person)
arrayOfArray.add(encodedObject)
}
var tempContacts2 = [Person]()
for data in arrayOfArray {
let person: Person = NSKeyedUnarchiver.unarchiveObject(with: data as! Data) as! Person
tempContacts2.append(person)
}
Note 2: if you absolutely wants to use NSMutableArrays that's possible too, just define tempContacts like this:
let tempContacts = NSMutableArray(array: [Person(name: "John"), Person(name: "Mary")])
The rest is working without changes.
Note 3: The reason it used to work in Swift 2 and it's not working anymore in Swift 3 is that the signature for the NSCoder method func encode(with coder:) changed in Swift 3.
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.
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)
}
}