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
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 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.
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.
I have a code that is executed in the host application and widget. All works in the main application, but one line in the widget - no
required init(coder aDecoder: NSCoder) {
name = aDecoder.decodeObject(forKey: "Name") as! String
ip[0] = UInt8(aDecoder.decodeInteger(forKey: "IP0"))
ip[1] = UInt8(aDecoder.decodeInteger(forKey: "IP1"))
ip[2] = UInt8(aDecoder.decodeInteger(forKey: "IP2"))
ip[3] = UInt8(aDecoder.decodeInteger(forKey: "IP3"))
NSKeyedUnarchiver.setClass(LEDMode.self, forClassName: "LEDMode")
if let saved = aDecoder.decodeObject(forKey: "Mode") as? NSData {
mode = NSKeyedUnarchiver.unarchiveObject(with: saved as Data) as! [LEDMode]
}
client = getTCPClient(ip)
}
widget crash in this line
mode = NSKeyedUnarchiver.unarchiveObject(with: saved as Data) as! [LEDMode]
message in debugger
2016-07-02 21:09:27.276947 LEDWidget[4616:272955]
* Terminatingapp due to uncaught exception 'NSInvalidUnarchiveOperationException',
reason: '* -[NSKeyedUnarchiver decodeObjectForKey:]:
cannot decode object of class (LEDControl.SingleColor) for key (NS.objects);
the class may be defined in source code or a library that is not linked
class SingleColor diclared
class SingleColor: LEDMode
class LEDMode diclared
class LEDMode: NSObject, NSCoding
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)
}
}