How to save multiple object in single UserDefaults

238 Views Asked by At

I've created NSObject class for "CurrentUser",

here, sign-up API will call & response data will insert into the "CurrentUser" model.

This is one type of global model, like in any screen user_logic_details will fetch, modify & save.

Example :

let loginuser : CurrentUser = CurrentUser.getLoginData()!
loginuser.email = "[email protected]"
CurrentUser.saveLoginData(loginData: loginuser)


// working
let email = CurrentUser.getLoginData()?.email ?? "" // i'll get "[email protected]"

in the above example if I write the below code then it shows nil data

let loginuser : CurrentUser = CurrentUser.getLoginData()!
loginuser.email = "[email protected]"
CurrentUser.saveLoginData(loginData: loginuser)

// working
let email = CurrentUser.getLoginData()?.email ?? "" // i'll get "[email protected]"
// not working
let roleName = CurrentUser.getLoginData()?.roles?.name ?? "" // showing get ""

I'm not able to find exact issues here,

Check below the code of how I use the model class, saving data into UserDefaults & retrieve data from the model.

    class CurrentUser: NSObject, NSCoding, NSKeyedUnarchiverDelegate {
    
    var email : String!
    var roles : UserRole!
    
    private var _isLoggedIn = false
    
    required convenience init(coder aDecoder: NSCoder) {
        self.init()
        roles = aDecoder.decodeObject(forKey: "roles") as? UserRole
        email = aDecoder.decodeObject(forKey: "email") as? String
    }
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(email, forKey: "email")
        aCoder.encode(roles, forKey: "roles")
    }
    
    class func getLoginData() -> CurrentUser? {
        let userDefaults = UserDefaults.standard
        if let UserData = userDefaults.object(forKey: "loginUser") {
            guard let unarchivedFavorites = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(UserData as! Data)
            else {
                return nil
            }
            return unarchivedFavorites as? CurrentUser
        } else {
            return CurrentUser()
        }
    }
    
    class func saveLoginData(loginData: CurrentUser?) {
        do {
            let encodedData = try NSKeyedArchiver.archivedData(withRootObject: loginData as Any, requiringSecureCoding: false)
            let userDefaults: UserDefaults = UserDefaults.standard
            userDefaults.set(encodedData, forKey: "loginUser")
            userDefaults.synchronize()
        } catch {
            print("Couldn't write file")
        }
    }
    
    func unarchiver(_ unarchiver: NSKeyedUnarchiver, cannotDecodeObjectOfClassName name: String, originalClasses classNames: [String]) -> AnyClass? {
        print("classNames", classNames)
        return nil
    }
}

class UserRole: NSObject, NSCoding  {
    
    var name : String!
    
    required convenience init(coder aDecoder: NSCoder) {
        self.init()
        name = aDecoder.decodeObject(forKey: "name") as? String
    }
    
    func encode(with aCoder: NSCoder) {
        aCoder.encode(name, forKey: "name")
    }
}

The above model is for reference purposes.

I need to convert the below Response to the whole model class

{
  "addressList": [
    {
      "addressLabel": "string",
      "city": "string",
      "country": "string",
      "createdAt": "2022-03-16T12:10:41.148Z",
      "homePhone": "string",
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "isDefault": true,
      "isDeleted": true,
      "state": "string",
      "streetAddress": "string",
      "updatedAt": "2022-03-16T12:10:41.148Z",
      "version": 0,
      "zip": "string"
    }
  ],
  "ageRange": "string",
  "badges": [
    {
      "badge": {
        "createdAt": "2022-03-16T12:10:41.148Z",
        "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "image": "string",
        "isDeleted": true,
        "label": "string",
        "name": "string",
        "status": "ACTIVE",
        "updatedAt": "2022-03-16T12:10:41.148Z",
        "users": [
          null
        ],
        "version": 0
      },
      "userBadgeId": {
        "badgeId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
        "userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
      }
    }
  ],
  "classesTaken": 0,
  "createdAt": "2022-03-16T12:10:41.148Z",
  "deviceList": [
    {
      "createdAt": "2022-03-16T12:10:41.148Z",
      "deviceId": "string",
      "deviceName": "string",
      "deviceType": "ANDROID",
      "display": "string",
      "fcmToken": "string",
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "isDeleted": true,
      "osVersion": "string",
      "updatedAt": "2022-03-16T12:10:41.148Z",
      "version": 0
    }
  ],
  "displayName": "string",
  "displayPic": "string",
  "dob": "2022-03-16T12:10:41.148Z",
  "email": "string",
  "experience": 0,
  "firebaseToken": "string",
  "firebaseUserId": "string",
  "followerCount0l": 0,
  "fullName": "string",
  "gender": "FEMALE",
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "isDeleted": true,
  "lastLoginTime": "2022-03-16T12:10:41.148Z",
  "location": "string",
  "phoneNumber": "string",
  "roles": [
    {
      "createdAt": "2022-03-16T12:10:41.148Z",
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "isDeleted": true,
      "name": "ROLE_ADMIN",
      "updatedAt": "2022-03-16T12:10:41.148Z",
      "version": 0
    }
  ],
  "rubyBalance": 0,
  "signupDateTime": "2022-03-16T12:10:41.148Z",
  "signupSource": {
    "buildNumber": "string",
    "deviceId": "string",
    "deviceType": "ANDROID",
    "osVersion": "string"
  },
  "status": "ACTIVE",
  "studentsTaught": 0,
  "updatedAt": "2022-03-16T12:10:41.148Z",
  "userInterest": [
    "string"
  ],
  "userName": "string",
  "version": 0
}

If anyone has a better way to do it or a better solution, please answer it.

1

There are 1 best solutions below

1
Shadowrun On

Your code reads like Objective-C from a few years ago. Something more modern and Swift-like might be:

@propertyWrapper
struct UserDefaultsCodableWrapper<T: Codable> {

    let key: String
    let defaultValue: T?

    init(_ key: String, defaultValue: T?) {
        self.key = key
        self.defaultValue = defaultValue
    }

    var wrappedValue: T? {
        get {
            guard let data = UserDefaults.standard.data(forKey: key),
                  let decoded = try? JSONDecoder().decode(T.self, from: data)
            else { return defaultValue }

            return decoded
        }
        set {
            let encoded = try? JSONEncoder().encode(newValue)
            UserDefaults.standard.set(encoded, forKey: key)
        }
    }

}


struct User: Codable {
    var email: String // TODO make a codable "Email" struct type that validates its input, no invalid strings allowed
    var roles: [UserRole]
}

struct UserRole: Codable {
    var name: String
}

class AppState {
    @UserDefaultsCodableWrapper("CurrentUser", defaultValue: nil)
    var currentUser: User?
}


class ViewController: UIViewController {

    let state = AppState()
    override func viewDidLoad() {
        super.viewDidLoad()

        print(String(describing: state.currentUser)) // nil
        state.currentUser = User(email: "[email protected]", roles: [.init(name: "Foo"), .init(name: "Bar")])
        print(String(describing: state.currentUser)) // Optional(User...

Also worth pointing you to app.quicktype.io which can generate you codable models from your json.