Save struct in class to NSUserDefaults using Swift

12,605

Solution 1

Swift structs are not classes, therefore they don't conform to AnyObject protocol. You have to rethink your approach. Here are some suggestions:

  1. Convert your struct to final class to enforce immutability

    final class MyStruct {
        let start : NSDate = NSDate()
        let stop : NSDate = NSDate()
    }
    
    encoder.encodeObject(mystructs)
    
  2. Map them as an array dictionaries of type [String: NSDate]

    let structDicts = mystructs.map { ["start": $0.start, "stop": $0.stop] }
    encoder.encodeObject(structDicts)
    

Solution 2

NSUserDefaults is limited in the types it can handle: NSData, NSString, NSNumber, NSDate, NSArray, NSDictionary, and Bool. Thus no Swift objects or structs can be saved. Anything else must be converted to an NSData object.

NSUserDefaults does not work the same way as NSArchiver. Since you already have added NSCoder to your classes your best choice might be to save and restore with NSArchiver to a file in the Documents directory..

From the Apple NSUserDefaults Docs:

A default object must be a property list, that is, an instance of (or for collections a combination of instances of): NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData.

Share:
12,605
Thomas
Author by

Thomas

Follow me on @thomasgruebler

Updated on June 14, 2022

Comments

  • Thomas
    Thomas about 2 years

    I have a class and inside the class is a (swift) array, based on a global struct. I want to save an array with this class to NSUserDefaults. This is my code:

    struct mystruct {
        var start : NSDate = NSDate()
        var stop : NSDate = NSDate()
    }
    
    class MyClass : NSObject {
    
        var mystructs : [mystruct]
    
        init(mystructs : [mystruct]) {
    
            self.mystructs = mystructs 
            super.init()
        }
    
        func encodeWithCoder(encoder: NSCoder) {
            //let val = mystructs.map { $0 as NSObject } //this also doesn't work
            let objctvtmrec = NSMutableArray(mystructs)  //gives error
            encoder.encodeObject(objctvtmrec)
            //first approach:
            encoder.encodeObject(mystructs) //error: [mystructs] doesn't conform to protocol 'anyobject'
        }
    
    }
    
    var records : [MyClass] {
        get {
            var returnValue : [MyClass]? = NSUserDefaults.standardUserDefaults().objectForKey("records") as? [MyClass]
            if returnValue == nil
            {
                returnValue = []
            }
            return returnValue!
        }
        set (newValue) {
            let val = newValue.map { $0 as AnyObject }
            NSUserDefaults.standardUserDefaults().setObject(val, forKey: "records")
            NSUserDefaults.standardUserDefaults().synchronize()
        }
    }
    

    I already subclassed to NSObject, and I know I need NSCoding. But I don't find any way to convert the struct array to an NSMuteableArray or something similar I can store. The only idea until now is to go through each entry and copy it directly to a new array or to use much or objective-c code all over the project, so i never need to convert from swift arrays to objective-c arrays. Both are things I don't want to do.

  • Thomas
    Thomas almost 10 years
    let structDicts = mystructs.map { ["start": $0.start, "stop": $0.stop] } -> fatal error: NSArray element failed to match the Swift Array Element type