L.William
L.William

Reputation: 432

Saving object to be accessible anywhere

I have a condition hereby let's say a user has logged in by calling an API. And the response contain's user's details. Is there anyway we can save the details of the user as an object and be accessible globally? Below is how I used ObjectMapper to call the api For the model class:

import ObjectMapper

class User: Mappable {

var id: Int?
var userId: Int?
var orgId: Int?
var type: Int?
var email: String?
var lang: String?
var nickname: String?
var status: Int?
var token: String?

required init(map: Map) {
    mapping(map: map)
}

func mapping(map: Map) {
    id <- map["id"]
    userId <- map["userId"]
    orgId <- map["orgId"]
    type <- map["type"]
    failedAttempt <- map["failedAttempt"]
    email <- map["email"]
    lang <- map["lang"]
    nickname <- map["nickname"]
    status <- map["status"]
    token <- map["token"]
}

}

And from my Networking file,

    func postLogin(params: [String: Any], controller: UIViewController, completion: @escaping (User) -> Void) {
    alamofire("/login", method: .post, token: false, params: params, controller: controller) { result in
        if let userDetails = Mapper<User>().map(JSON: result as! [String: Any]) {
            DispatchQueue.main.async {
                completion(userDetails)
            }
        }
    }
}

Some solutions may be using UserDefaults but it's just not practical to be using 9 UserDefaults to save the 9 keys that we got from this response. What are the suggested ways of approach we can go about this where user logged in, we save these details as an object globally and even when after closing the app, the details are not reseted? Thank you all

Upvotes: 0

Views: 124

Answers (2)

Blazej SLEBODA
Blazej SLEBODA

Reputation: 9935

You have to ask yourself how you would like to access this object with compilator autocompletion from different places in your app. Secondly, what is the life span of the object? The object should be accessible during login / logout, app session or app life time?

Let's start with the object life span.

  • login / logout, the object can be stored in memory
  • app session, the object can be stored in memory
  • app install / delete, the object should be stored in UserDefaults, Database or a file.

Autocompletion Driven Development aka ADD:

Deciding where to store userDetails object and how to access it

Let's say that you have a logic in a view controller which hide or unhide a view in a initial configuration defined in viewDidLoad. Write some prototype code to decide how you would like to access the 'userDetails' object from different places of the app.

func viewDidLoad() {
   super.viewDidLoad()

   if userDetails.status {
     //userDetails is a global instance of a user object
   }

   if session.userDetails.status {
     //session is a global instance of a Session object which has a property which stores a User instance in userDetails
   }

   if Session.current.userDetails.status {
     // Session is a type and 'current' is a singleton of that type which has a userDetails property which stores an instance of User type
   }

   if User.userDetails.status {
     // User is your type which can have a static property 'userDetails' which stores a User instance
   }

}

Upvotes: 0

Chip Jarred
Chip Jarred

Reputation: 2805

I agree that saving 9 individual keys in UserDefaults is not practical. But why not encode the whole thing as JSON and save the resulting Data?

extension User: Codable { }

// Assuming user is an instance of User
guard let userJSON = try? JSONEncoder().encode(user) else {
    // handle encoding failure
}

let userDefaultsKeyForUser = "com.yourdomain.snazzyapp.userInfo"

UserDefaults.standard.set(userJSON, forKey: userDefaultsKeyForUser)

I think Swift will automatically synthesize Codable conformance for your User class, but you might need to make it final. Or implement Codable explicitly.

To retrieve it

guard let userJSON = UserDefaults.standard.data(forKey: userDefaultsKeyForUser) else {
    // Handle missing data (first run maybe?)
}

guard let user = try? JSONDecoder().decode(User.self, from: userJSON) else {
   // Handle decoding failure
} 

Alternative encoding/decoding methods

Although conforming to Codable would be the preferred way to do this, that can sometimes be a problem for classes, particularly when inheritance is involved. In particular Codable is a combination of Encodable and Decodable. Decodable is the one that is sometimes a problem for classes. The issue has to do with the required init(from decoder: Decoder) throws having to be declared directly in the class, not in an extension, and then you might have trouble with encoding the super. If your class is the base class that shouldn't be a problem. This is mainly a problem when you inherit from AppKit/UIKit classes.

If for some reason User can't conform to Codable, you can use NSCoding instead. It works a little differently.

In the worst case you could implement methods where you manually encode/decode each property explicitly to data. You could even store them as binary, but you'll probably need to do something like store byte counts for the strings so you know where each one starts and ends, and you'll need to come up with a scheme to indicate when they are nil in the encoding. It's not as flexible as JSON, which is why it's a last resort... although I will note that it is much faster. That wouldn't matter much for your User class, but I wrote a neural network library where I wanted to save the model at checkpoints during training, which meant encoding many layers of very large matrices. Millions of parameters. Reading and writing the model was about 20x faster with my own binary encoding than letting Codable handle it, even when I had Codable saving it as a binary plist.

A third option would be to re-work your User class. Either make it a struct, or use a struct internally to store its properties and use computed properties to get and set them in your class. All of your class's properties already conform to Codable, so Swift can definitely synthesize Codable conformance for a struct with those properties. If you still wrap the struct inside of a class, then you just have an initializer that takes a Data, and do the same decoding I showed above, to set the wrapped struct, and an encode method (or even a computed var) that encodes the struct as above and returns the Data.

I don't think you'll need these alternate solutions, but I mention them just in case I'm wrong, and because they're useful to know about for future situations.

Upvotes: 2

Related Questions