Saving Realm Object subclass in NSUserDefaults? - ios

I'm introducing Realm into my swift project. I have a User class that I was saving an instance of into NSUserDefaults to keep track of the 1 logged in user.
After making User a subclass of Object, I get the following error when trying to unarchive (archiving seems to work OK):
Terminating app due to uncaught exception
'NSInvalidUnarchiveOperationException', reason: '***
-[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (RLMStandalone_User) for key (root); the class may be defined in
source code or a library that is not linked'
I have Realm installed as a Cocoapod, this are the relevant methods in the User clas
static var currentUser: User? {
get {
if let data = NSUserDefaults.standardUserDefaults().objectForKey(UserDefaultKeys.kUserData) as? NSData,
let user = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? User {
return user
} else {
return nil
}
}
set {
if let user = newValue {
let data = NSKeyedArchiver.archivedDataWithRootObject(user)
NSUserDefaults.standardUserDefaults().setObject(data, forKey: UserDefaultKeys.kUserData)
NSUserDefaults.standardUserDefaults().synchronize()
} else {
NSUserDefaults.standardUserDefaults().removeObjectForKey(UserDefaultKeys.kUserData)
NSUserDefaults.standardUserDefaults().synchronize()
}
}
}
// MARK: NSCoding
convenience init?(coder decoder: NSCoder) {
self.init()
guard let firstName = decoder.decodeObjectForKey("firstName") as? String,
let lastName = decoder.decodeObjectForKey("lastName") as? String,
let email = decoder.decodeObjectForKey("email") as? String,
let icloud = decoder.decodeObjectForKey("icloudUserID") as? String,
let userType = decoder.decodeObjectForKey("userType") as? String
else {
return nil
}
self.firstName = firstName
self.lastName = lastName
self.email = email
self.profilePic = decoder.decodeObjectForKey("profilePic") as? String
self.icloudUserID = icloud
self.userType = userType
self.coverPhoto = decoder.decodeObjectForKey("coverPhoto") as? String
self.facebookID = decoder.decodeObjectForKey("facebookID") as? String
self.placeID = decoder.decodeObjectForKey("placeID") as? String
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.firstName, forKey: "firstName")
coder.encodeObject(self.lastName, forKey: "lastName")
coder.encodeObject(self.icloudUserID, forKey: "icloudUserID")
coder.encodeObject(self.userType, forKey: "userType")
coder.encodeObject(email, forKey: "email")
if let coverPhotoUrl = self.coverPhotoUrl {
coder.encodeObject(coverPhotoUrl, forKey: "coverPhoto")
}
if let profilePicUrl = self.profilePicUrl {
coder.encodeObject(profilePicUrl, forKey: "profilePic")
}
if let fbID = self.facebookID {
coder.encodeObject(fbID, forKey: "facebookID")
}
if let placeID = self.placeID {
coder.encodeObject(placeID, forKey: "placeID")
}
}

It's not possible to store a Realm Object in NSUserDefaults as (As you saw in that error message) they cannot be serialized or deserialized by the NSCoding protocol.
Instead, it might be better to add a primary key property to your User object (So you can use it to query that exact object from Realm), and store the primary key itself in NSUserDefaults instead.
Or better yet, instead of relying on NSUSerDefaults, it might be better to simply have a boolean property, isCurrent in your model, and using that to work out which user is the current one.

You should keep your NSUserDefaults and your Realm data separate. They are two different methods of persistent data storage. If you've converted something to a Realm Object, you no longer need to (or should) try and put that into NSUserDefaults.

Related

fatal error: unexpectedly found nil while unwrapping an Optional value in Swift 3

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.

Saving Custom Object (within another custom object) to User Defaults in Swift 3

I have created two types of custom objects:
The first object, called Contact, has 4 fields which store Strings.
The second object, called ContactList, has 1 field which stores a list of my first object [Contact].
In my View Controller, I have an instance of a ContactList that I would like to save into User Defaults. Looking through other questions, I have added the following methods into the Contact class (as well as making it inherit from NSObject, and NSCoding)
required init(coder aDecoder: NSCoder) {
self.firstName = (aDecoder.decodeObject(forKey: "first") as? String)!
self.lastName = (aDecoder.decodeObject(forKey: "last") as? String)!
self.phoneNumber = (aDecoder.decodeObject(forKey: "phone") as? String)!
self.email = (aDecoder.decodeObject(forKey: "email") as? String)!
}
func encode(with aCoder: NSCoder){
aCoder.encode(self.firstName, forKey: "first")
aCoder.encode(self.lastName, forKey: "last")
aCoder.encode(self.phoneNumber, forKey: "phone")
aCoder.encode(self.email, forKey: "email")
}
And then in my ContactList class, I have added two functions:
func saveData(){
let data = NSKeyedArchiver.archivedData(withRootObject: list)
let defaults = UserDefaults.standard
defaults.set(data, forKey:"contacts" )
}
func retrieveData(){
if let data = UserDefaults.standard.object(forKey: "contacts") as? NSData
{
list = NSKeyedUnarchiver.unarchiveObject(with: data as Data) as! [Contact]
}
}
In my View Controller, I'm calling saveData() on my current instance of ContactList. In my ViewDidLoad method, I have an assignment statement to my variable which holds the instance of this by creating a new instance and then calling retrieveData() on it.
However, when I run my program and add elements to the list in ContactList's list field, exit out, and then come back into the app, the elements that I added is not there (I have a table that updates and displays the contents of the list in the ContactList).
Am I supposed to have ContactList inherit something, or am I just implementing these methods wrong? This is the first time that I'm using UserDefaults, so any help would be greatly appreciated!

Saving custom NSObject in NSUserDefaults

I'm having a modal entity file as below,
import UIKit
class MyProfile: NSObject {
var userName : String = ""
func initWithDict(dict: NSMutableDictionary) {
self.userName = dict.objectForKey("username") as! String
}
}
Saving that entity by encoding as below,
let myDict: NSMutableDictionary = ["username": "abc"]
let myEntity:MyProfile = MyProfile()
myEntity.initWithDict(myDict)
let userDefaults = NSUserDefaults.standardUserDefaults()
let encodedData = NSKeyedArchiver.archivedDataWithRootObject(myEntity)
userDefaults.setObject(encodedData, forKey: "MyProfileEntity")
userDefaults.synchronize()
Getting that saved entity as below,
let myEntity:MyProfile = MyProfile()
let userDefaults = NSUserDefaults.standardUserDefaults()
guard let decodedNSData = userDefaults.objectForKey("MyProfileEntity") as? NSData,
myEntity = NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSData) as? MyProfile!
else {
print("Failed")
return
}
print(myEntity.userName)
It's not working, having crashes and lot of syntax errors, I'm new to swift,
It's showing some syntax errors like definition conflicts with previous value in the unarchiveObjectWithData line. If I fix that error, then at the time of getting the entity from userdefaults it's crashing.
can anyone suggest how can I resolve it?
To save custom object into user default, you must implement NSCoding protocol. Please replace your custom data model like this:
class MyProfile: NSObject,NSCoding {
var userName : String = ""
#objc required init(coder aDecoder:NSCoder){
self.userName = aDecoder.decodeObjectForKey("USER_NAME") as! String
}
#objc func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.userName, forKey: "USER_NAME")
}
init(dict: [String: String]) {
self.userName = dict["username"]!
}
override init() {
super.init()
}
}
Here is the code for saving and retrieving MyProfile object:
// Save profile
func saveProfile(profile: MyProfile){
let filename = NSHomeDirectory().stringByAppendingString("/Documents/profile.bin")
let data = NSKeyedArchiver.archivedDataWithRootObject(profile)
data.writeToFile(filename, atomically: true)
}
// Get profile
func getProfile() -> MyProfile?{
if let data = NSData(contentsOfFile: NSHomeDirectory().stringByAppendingString("/Documents/profile.bin")){
let unarchiveProfile = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! MyProfile
return unarchiveProfile
} else{
return nil
}
}
Now here is the code snippet how to use those method:
// Create profile object
let profile = MyProfile(dict: ["username" : "MOHAMMAD"])
// save profile
saveProfile(profile)
// retrieve profile
if let myProfile = getProfile(){
print(myProfile.userName)
}else{
print("Profile not found")
}
You can't do this:
let myEntity:MyProfile = MyProfile()
Then later on, do this:
myEntity = ...
When something is defined with 'let', you cannot change it.
Change to
var myEntity: MyProfile?
It is possible that
NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSData)
is returning nil. You then proceed to force unwrapping by adding
as? MyProfile!
try changing this to
as? MyProfile
Then later, see if you got something back
if let myEntity = myEntity {
print(myEntity.userName)
}

How can I use UserDefaults in Swift?

How can I use UserDefaults to save/retrieve strings, booleans and other data in Swift?
ref: NSUserdefault objectTypes
Swift 3 and above
Store
UserDefaults.standard.set(true, forKey: "Key") //Bool
UserDefaults.standard.set(1, forKey: "Key") //Integer
UserDefaults.standard.set("TEST", forKey: "Key") //setObject
Retrieve
UserDefaults.standard.bool(forKey: "Key")
UserDefaults.standard.integer(forKey: "Key")
UserDefaults.standard.string(forKey: "Key")
Remove
UserDefaults.standard.removeObject(forKey: "Key")
Remove all Keys
if let appDomain = Bundle.main.bundleIdentifier {
UserDefaults.standard.removePersistentDomain(forName: appDomain)
}
Swift 2 and below
Store
NSUserDefaults.standardUserDefaults().setObject(newValue, forKey: "yourkey")
NSUserDefaults.standardUserDefaults().synchronize()
Retrieve
var returnValue: [NSString]? = NSUserDefaults.standardUserDefaults().objectForKey("yourkey") as? [NSString]
Remove
NSUserDefaults.standardUserDefaults().removeObjectForKey("yourkey")
Register
registerDefaults: adds the registrationDictionary to the last item in every search list. This means that after NSUserDefaults has looked for a value in every other valid location, it will look in registered defaults, making them useful as a "fallback" value. Registered defaults are never stored between runs of an application, and are visible only to the application that registers them.
Default values from Defaults Configuration Files will automatically be registered.
for example detect the app from launch , create the struct for save launch
struct DetectLaunch {
static let keyforLaunch = "validateFirstlunch"
static var isFirst: Bool {
get {
return UserDefaults.standard.bool(forKey: keyforLaunch)
}
set {
UserDefaults.standard.set(newValue, forKey: keyforLaunch)
}
}
}
Register default values on app launch:
UserDefaults.standard.register(defaults: [
DetectLaunch.isFirst: true
])
remove the value on app termination:
func applicationWillTerminate(_ application: UIApplication) {
DetectLaunch.isFirst = false
}
and check the condition as
if DetectLaunch.isFirst {
// app launched from first
}
UserDefaults suite name
another one property suite name, mostly its used for App Groups concept, the example scenario I taken from here :
The use case is that I want to separate my UserDefaults (different business logic may require Userdefaults to be grouped separately) by an identifier just like Android's SharedPreferences. For example, when a user in my app clicks on logout button, I would want to clear his account related defaults but not location of the the device.
let user = UserDefaults(suiteName:"User")
use of userDefaults synchronize, the detail info has added in the duplicate answer.
Best way to use UserDefaults
Steps
Create extension of UserDefaults
Create enum with required Keys to
store in local
Store and retrieve the local data wherever you want
Sample
extension UserDefaults{
//MARK: Check Login
func setLoggedIn(value: Bool) {
set(value, forKey: UserDefaultsKeys.isLoggedIn.rawValue)
//synchronize()
}
func isLoggedIn()-> Bool {
return bool(forKey: UserDefaultsKeys.isLoggedIn.rawValue)
}
//MARK: Save User Data
func setUserID(value: Int){
set(value, forKey: UserDefaultsKeys.userID.rawValue)
//synchronize()
}
//MARK: Retrieve User Data
func getUserID() -> Int{
return integer(forKey: UserDefaultsKeys.userID.rawValue)
}
}
enum for Keys used to store data
enum UserDefaultsKeys : String {
case isLoggedIn
case userID
}
Save in UserDefaults where you want
UserDefaults.standard.setLoggedIn(value: true) // String
UserDefaults.standard.setUserID(value: result.User.id!) // String
Retrieve data anywhere in app
print("ID : \(UserDefaults.standard.getUserID())")
UserDefaults.standard.getUserID()
Remove Values
UserDefaults.standard.removeObject(forKey: UserDefaultsKeys.userID)
This way you can store primitive data in best
Update
You need no use synchronize() to store the values. As #Moritz pointed out the it unnecessary and given the article about it.Check comments for more detail
Swift 4 :
Store
UserDefaults.standard.set(object/value, forKey: "key_name")
Retrive
var returnValue: [datatype]? = UserDefaults.standard.object(forKey: "key_name") as? [datatype]
Remove
UserDefaults.standard.removeObject(forKey:"key_name")
UserDefault+Helper.swift
import UIKit
private enum Defaults: String {
case countryCode = "countryCode"
case userloginId = "userloginid"
}
final class UserDefaultHelper {
static var countryCode: String? {
set{
_set(value: newValue, key: .countryCode)
} get {
return _get(valueForKay: .countryCode) as? String ?? ""
}
}
static var userloginId: String? {
set{
_set(value: newValue, key: .userloginId)
} get {
return _get(valueForKay: .userloginId) as? String ?? ""
}
}
private static func _set(value: Any?, key: Defaults) {
UserDefaults.standard.set(value, forKey: key.rawValue)
}
private static func _get(valueForKay key: Defaults)-> Any? {
return UserDefaults.standard.value(forKey: key.rawValue)
}
static func deleteCountryCode() {
UserDefaults.standard.removeObject(forKey: Defaults.countryCode.rawValue)
}
static func deleteUserLoginId() {
UserDefaults.standard.removeObject(forKey: Defaults.userloginId.rawValue)
}
}
Usage:
Save Value:
UserDefaultHelper.userloginId = data["user_id"] as? String
Fetch Value:
let userloginid = UserDefaultHelper.userloginId
Delete Value:
UserDefaultHelper.deleteUserLoginId()
I would say Anbu's answer perfectly fine but I had to add guard while fetching preferences to make my program doesn't fail
Here is the updated code snip in Swift 5
Storing data in UserDefaults
#IBAction func savePreferenceData(_ sender: Any) {
print("Storing data..")
UserDefaults.standard.set("RDC", forKey: "UserName") //String
UserDefaults.standard.set("TestPass", forKey: "Passowrd") //String
UserDefaults.standard.set(21, forKey: "Age") //Integer
}
Fetching data from UserDefaults
#IBAction func fetchPreferenceData(_ sender: Any) {
print("Fetching data..")
//added guard
guard let uName = UserDefaults.standard.string(forKey: "UserName") else { return }
print("User Name is :"+uName)
print(UserDefaults.standard.integer(forKey: "Age"))
}
//Save
NSUserDefaults.standardUserDefaults().setObject("yourString", forKey: "YourStringKey")
//retrive
let yourStr : AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey("YourStringKey")
You can use NSUserDefaults in swift this way,
#IBAction func writeButton(sender: UIButton)
{
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject("defaultvalue", forKey: "userNameKey")
}
#IBAction func readButton(sender: UIButton)
{
let defaults = NSUserDefaults.standardUserDefaults()
let name = defaults.stringForKey("userNameKey")
println(name) //Prints defaultvalue in console
}
Swift 5 and above:
let defaults = UserDefaults.standard
defaults.set(25, forKey: "Age")
let savedInteger = defaults.integer(forKey: "Age")
defaults.set(true, forKey: "UseFaceID")
let savedBoolean = defaults.bool(forKey: "UseFaceID")
defaults.set(CGFloat.pi, forKey: "Pi")
defaults.set("Your Name", forKey: "Name")
defaults.set(Date(), forKey: "LastRun")
let array = ["Hello", "World"]
defaults.set(array, forKey: "SavedArray")
let savedArray = defaults.object(forKey: "SavedArray") as? [String] ?? [String()
let dict = ["Name": "Your", "Country": "YourCountry"]
defaults.set(dict, forKey: "SavedDict")
let savedDictionary = defaults.object(forKey: "SavedDictionary") as? [String: String] ?? [String: String]()
:)
I saved NSDictionary normally and able to get it correctly.
dictForaddress = placemark.addressDictionary! as NSDictionary
let userDefaults = UserDefaults.standard
userDefaults.set(dictForaddress, forKey:Constants.kAddressOfUser)
// For getting data from NSDictionary.
let userDefaults = UserDefaults.standard
let dictAddress = userDefaults.object(forKey: Constants.kAddressOfUser) as! NSDictionary
I have Created my Custom Functions for Store Data in Userdefualts
//******************* REMOVE NSUSER DEFAULT *******************
func removeUserDefault(key:String) {
UserDefaults.standard.removeObject(forKey: key);
}
//******************* SAVE STRING IN USER DEFAULT *******************
func saveInDefault(value:Any,key:String) {
UserDefaults.standard.setValue(value, forKey: key);
UserDefaults.standard.synchronize();
}
//******************* FETCH STRING FROM USER DEFAULT *******************
func fetchString(key:String)->AnyObject {
if (UserDefaults.standard.object(forKey: key) != nil) {
return UserDefaults.standard.value(forKey: key)! as AnyObject;
}
else {
return "" as AnyObject;
}
}
class UserDefaults_FavoriteQuote {
static let key = "appname.favoriteQuote"
static var value: String? {
get {
return UserDefaults.standard.string(forKey: key)
}
set {
if newValue != nil {
UserDefaults.standard.set(newValue, forKey: key)
} else {
UserDefaults.standard.removeObject(forKey: key)
}
}
}
}
In class A, set value for key:
let text = "hai"
UserDefaults.standard.setValue(text, forKey: "textValue")
In class B, get the value for the text using the key which declared in class A and assign it to respective variable which you need:
var valueOfText = UserDefaults.value(forKey: "textValue")
Swift 4,
I have used Enum for handling UserDefaults.
This is just a sample code. You can customize it as per your requirements.
For Storing, Retrieving, Removing.
In this way just add a key for your UserDefaults key to the enum.
Handle values while getting and storing according to dataType and your requirements.
enum UserDefaultsConstant : String {
case AuthToken, FcmToken
static let defaults = UserDefaults.standard
//Store
func setValue(value : Any) {
switch self {
case .AuthToken,.FcmToken:
if let _ = value as? String {
UserDefaults.standard.set(value, forKey: self.rawValue)
}
break
}
UserDefaults.standard.synchronize()
}
//Retrieve
func getValue() -> Any? {
switch self {
case .AuthToken:
if(UserDefaults.standard.value(forKey: UserDefaultsConstant.AuthToken.rawValue) != nil) {
return "Bearer "+(UserDefaults.standard.value(forKey: UserDefaultsConstant.AuthToken.rawValue) as! String)
}
else {
return ""
}
case .FcmToken:
if(UserDefaults.standard.value(forKey: UserDefaultsConstant.FcmToken.rawValue) != nil) {
print(UserDefaults.standard.value(forKey: UserDefaultsConstant.FcmToken.rawValue))
return (UserDefaults.standard.value(forKey: UserDefaultsConstant.FcmToken.rawValue) as! String)
}
else {
return ""
}
}
}
//Remove
func removeValue() {
UserDefaults.standard.removeObject(forKey: self.rawValue)
UserDefaults.standard.synchronize()
}
}
For storing a value in userdefaults,
if let authToken = resp.data?.token {
UserDefaultsConstant.AuthToken.setValue(value: authToken)
}
For retrieving a value from userdefaults,
//As AuthToken value is a string
(UserDefaultsConstant.AuthToken.getValue() as! String)
use UserDefault to store any settings value you want your application to remember between start ups, maybe you want to know ifs its been started before, maybe you want some values the user has set to be remembers so they don't have to be set very time, on Mac windows frames are stored in there for you, maybe you want to control the behaviour of the app, but you don't want it available to end users, you just want to choose just before your release. Be careful what you store in UserDefaults, it's not protected.

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