Reputation: 432
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
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.
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
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
}
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