Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Saving Array of Struct with NSKeyedArchiver and Userdefault in which object conform to NSCoding

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?

like image 264
Wolverine Avatar asked Nov 24 '16 07:11

Wolverine


People also ask

How do you save an array of objects in UserDefaults in Swift?

Let's take a look at an example. We access the shared defaults object through the standard class property of the UserDefaults class. We then create an array of strings and store the array in the user's defaults database by invoking the set(_:forKey:) method of the UserDefaults database.

What is NSCoding?

NSCoding is a protocol that you can implement on your data classes to support the encoding and decoding of your data into a data buffer, which can then persist on disk. Implementing NSCoding is actually ridiculously easy — that's why you may find it helpful to use.

When to use NSKeyedArchiver?

You can use NSCoding and NSKeyedArchiver to save and load simple data objects with Swift. It's perfect for scenarios when you don't need a more complex tool, like Core Data or Realm.

How does NSKeyedArchiver work?

NSKeyedArchiver , a concrete subclass of NSCoder , provides a way to encode objects (and scalar values) into an architecture-independent format suitable for storage in a file. When you archive a set of objects, the archiver writes the class information and instance variables for each object to the archive.


2 Answers

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())
like image 102
Casey Avatar answered Nov 01 '22 03:11

Casey


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
like image 27
Shrikant Tanwade Avatar answered Nov 01 '22 03:11

Shrikant Tanwade