7

As a Protocol oriented programming concept, I have created my model with Struct.

I want to save Array of "Struct" into Userdefault. But I am having a problem in encode/decode of the array of this model.

Here is my model Struct

struct Room {
    let name : String
    let id : String
    let booked : Bool
}

Here I created a extension like this

extension Room {


func decode() -> Room? {
    let userClassObject = NSKeyedUnarchiver.unarchiveObject(withFile: RoomClass.path()) as? RoomClass
    return userClassObject?.room
}

func encode() {
    let personClassObject = RoomClass(room: self)
    NSKeyedArchiver.archiveRootObject(personClassObject, toFile: RoomClass.path())
}

class RoomClass: NSObject, NSCoding {

    var room : Room?

    init(room: Room) {
        self.room = room
        super.init()
    }

    class func path() -> String {
        let documentsPath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
        let path = documentsPath?.appending(("/Room"))
        return path!
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(room!.name, forKey: "name")
        aCoder.encode(room!.id, forKey: "Id")
        aCoder.encode(room!.booked, forKey: "booked")
    }

    required init?(coder aDecoder: NSCoder) {
        let _name = aDecoder.decodeObject(forKey: "name") as? String
        let _id = aDecoder.decodeObject(forKey: "Id") as? String
        let _booked = aDecoder.decodeBool(forKey: "booked")

        room = Room(name: _name!, id: _id!, booked: _booked)

        super.init()
    }
}
}

When I am trying to save arrRoomList(a Array of Room objects) like this

        self.saveRooms(arrayRooms: arrRoomList)

I got this error

[_SwiftValue encodeWithCoder:]: unrecognized selector sent to instance

I have also tried to encode each object first and then try to save them in default, then also it gives an error.

Can anyone please guide me how to encode/decode the array of Struct in Userdefaults in a proper way without converting it into Dictionary?

Wolverine
  • 4,264
  • 1
  • 27
  • 49
  • Look complicated, you are converting your struct to class to encode and save? – Tj3n Nov 24 '16 at 07:14
  • 1
    @Idan : I have modify my question with more details. I want to save Struct to Defaults in which object is confirming NSCoding – Wolverine Nov 24 '16 at 07:22
  • Dump NSCoding and stuff, just convert your object to Dictionary, then save it, very easy, the amount of code is similar also – Tj3n Nov 24 '16 at 07:27
  • @Tj3n : Is that will be good practice ? I am not sure that's why I am asking. – Wolverine Nov 24 '16 at 07:28
  • Why do you think its not good? Basically its what NSCoding do also I think, but its just encode them to NSData after everything – Tj3n Nov 24 '16 at 07:29
  • @Tj3n May be you are right. also NSCoding is used for object oriented design pattern. Lets see if there is any official persistence method introduced for swift with Protocol oriented programming concept. – Wolverine Nov 24 '16 at 07:36
  • The official method for persistent data is CoreData, this is just for storing very simple stuff like setting or something, also, protocol oriented doesnt mean you have to use struct, it doesnt matter, it just mean you stop subclassing and split the object's ability into multiple protocol, like Array confront Collection and it confront Index and Sequence,... – Tj3n Nov 24 '16 at 07:44
  • As per I think choice of Struct and class matters. Structs are preferable if they are relatively small and copiable because copying is way safer than having multiple reference to the same instance as happens with classes. This is especially important when passing around a variable to many classes and/or in a multithreaded environment. – Wolverine Nov 24 '16 at 08:58
  • Check out [Elevate](https://github.com/Nike-Inc/Elevate), you can use to it encode or decode objects to JSON which you can then save to user defaults. It's very useful. – par Dec 22 '16 at 07:19

2 Answers2

11

you can setup the struct to use NSKeyedArchiver directly like this:

struct Room {
    let name : String
    let id : String
    let booked : Bool
}

extension Room {
    func encode() -> Data {
        let data = NSMutableData()
        let archiver = NSKeyedArchiver(forWritingWith: data)
        archiver.encode(name, forKey: "name")
        archiver.encode(id, forKey: "id")
        archiver.encode(booked, forKey: "booked")
        archiver.finishEncoding()
        return data as Data
    }

    init?(data: Data) {
        let unarchiver = NSKeyedUnarchiver(forReadingWith: data)
        defer {
            unarchiver.finishDecoding()
        }
        guard let name = unarchiver.decodeObject(forKey: "name") as? String else { return nil }
        guard let id = unarchiver.decodeObject(forKey: "id") as? String else { return nil }
        booked = unarchiver.decodeBool(forKey: "booked")
        self.name = name
        self.id = id
    }
}

to use with UserDefaults, call like this:

// to encode to data and save to user defaults
let room = Room(name: "asdf", id: "123", booked: true)
UserDefaults.standard.set(room.encode(), forKey: "room")

// to retrieve from user defaults
if let data = UserDefaults.standard.object(forKey: "room") as? Data {
    let room = Room(data: data)
}

Can save/retrieve an array of rooms like this:

func saveRooms(arrayRooms: [Room]) {
    let roomsData = arrayRooms.map { $0.encode() }
    UserDefaults.standard.set(roomsData, forKey: "rooms")
}

func getRooms() -> [Room]? {
    guard let roomsData = UserDefaults.standard.object(forKey: "rooms") as? [Data] else { return nil }
    return roomsData.flatMap { return Room(data: $0) }
}


// save 2 rooms to user defaults
let roomA = Room(name: "A", id: "123", booked: true)
let roomB = Room(name: "B", id: "asdf", booked: false)
saveRooms(arrayRooms: [roomA, roomB])

// get the rooms
print(getRooms())
Casey
  • 6,531
  • 24
  • 43
1

You can try Model like

class CardModel: NSObject
{
    let name : String
    let id : String
    let booked : Bool

    override init()
    {
        self.name = ""
        self.id = ""
        self.booked = false
    }

    required init(coder aDecoder: NSCoder)
    {
        self.name = aDecoder.decodeObject(forKey: "name") as! String
        self.id = aDecoder.decodeObject(forKey: "id") as! String
        self.booked = aDecoder.decodeObject(forKey: "booked") as! Bool
     }

    func encodeWithCoder(_ aCoder: NSCoder)
    {
        aCoder.encode(name, forKey: "name")
        aCoder.encode(id, forKey: "id")
        aCoder.encode(booked, forKey: "booked")
    }
}

Use by Creating CardModel model Object

let objCardModel = CardModel()
objCardModel.name = "Shrikant"
objCardModel.id = "8"
objCardModel.booked = true

Access by object

let userName = objCardModel.name
Shrikant Tanwade
  • 1,391
  • 12
  • 21
  • 1
    Yes, but As a Protocol oriented programming concept, I have created my model with Struct. I know i can achieve this by creating only class. Is there anyway we can do this for Struct ? – Wolverine Nov 24 '16 at 07:27
  • 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. – Shrikant Tanwade Nov 24 '16 at 07:42
  • 1
    Where and how would these pieces of data be saved and retrieved? – Confused Dec 22 '16 at 03:33