Swift Singleton class to store user settings - ios

I am trying to create an AppSettings class to store user setttings in an user dictionary. My AppSettings class:
class AppSettings : NSObject
{
static var user: [String: Any]?
static let sharedSingleton : AppSettings = {
let instance = AppSettings()
return instance
}()
var userID: String?
var baseUrl: String?
override private init() {
super.init()
let userDefaults = UserDefaults.standard
AppSettings.user = userDefaults.object(forKey: "user") as! [String : Any]?
print(AppSettings.user ?? "User Defaults has no values")
self.saveSettings()
}
func saveSettings()
{
let userDefaults = UserDefaults.standard
if((AppSettings.user) != nil)
{
userDefaults.set(AppSettings.user, forKey: "user")
}
userDefaults.synchronize()
}
}
I am storing values in user this way:
AppSettings.user?["User_ID"] = String(describing: dictionary["User_ID"])
AppSettings.sharedSingleton.saveSettings()
let defaults = UserDefaults.standard.object(forKey: "user")
print(defaults ?? "User is null")
It shows that User is null. What's wrong?

Assuming your example is complete, the problem lies in this line
AppSettings.user?["User_ID"] = String(describing: dictionary["User_ID"])
The user variable is an optional, so unless you created an empty dictionary first this is what happens - if user is not nil, assign a value under "User_ID" key.
All you need to do is write AppSetting.user = [String:Any]() before trying to add values to it. Now, it may seem that you do it in init(), but it's not called unless you call sharedInstace.
On a side note - if you are making this a singleton, why do you expose the user variable as a static?

Related

How to access model class values in another controller using singleton in swift?

I have created a class with some variables and have not initialized with any default value, all variables should be assigned when web service call occurs. So I have initialized the instance of the class and assigned the value to the variables.
Here I want to access these values to all class file throughout the project. Is it possible? I do not want to use any saving methods like core data and user defaults also codable local storage.
Please help me out with this? We tried to access the model class value in another view controller. But we get a nil value. Thanks in Advance.
//MARK: Shared Instance
static let sharedInstance = Singleton()
//MARK: Local Variable
var emptyStringArray : [String]? = nil
var completed : Bool!
var id : Int?
var title : String?
var userId : Int?
//MARK: Init Array
private init() {
}
init(Fromarray dictionary : [String:Any]) {
completed = dictionary["completed"] as? Bool
id = dictionary["id"] as? Int
title = dictionary["title"] as? String
userId = dictionary["userId"] as? Int
}
finally called in
class ViewController2: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let BoolValue = Singleton.sharedInstance.completed
print(BoolValue)
}
This is a very poor architectural decision.
Among the many the first problem here is that results of API calls are always asynchronous so you can't tell if your class have been properly initialized at the moment you use it's properties.
You create too much ambiguity by creating forced unwrapped optional that depends on network call. What if API call fails? What if internet connection is slow, how would you predict that your singleton is "fine" at this moment? For most of the cases you will be accessing nil and it will crash your app.
I'd suggest more reading on Singleton pattern (if it's necessary) and also on architectural patterns in iOS.
use different function to load data and call that after WebCall
func loadData(Fromarray dictionary : [String:Any]) {
completed = dictionary["completed"] as? Bool
id = dictionary["id"] as? Int
title = dictionary["title"] as? String
userId = dictionary["userId"] as? Int
}
call after web call completion
Singleton.sharedInstance.loadData(FromArray : YourDictoinary)
and acess anywhre throught project
let title = Singleton.sharedInstance.title
You need to keep a single object of your singleton class.
static let sharedInstance = Singleton()
It will create a object of your singleton class.
private init() { } or,
init(fromarray dictionary: [String:Any]) {
completed = dictionary["completed"] as? Bool
id = dictionary["id"] as? Int
title = dictionary["title"] as? String
userId = dictionary["userId"] as? Int
}
It will create another object of your singleton class, which will be differ form above.
If you need to access the data from your singleton class. Create your class like:
class Singleton {
static let sharedInstance = Singleton()
var emptyStringArray: [String]?
var completed: Bool?
var id: Int?
var title: String?
var userId: Int?
func initializeData(_ dictionary: [String:Any]) {
completed = dictionary["completed"] as? Bool
id = dictionary["id"] as? Int
title = dictionary["title"] as? String
userId = dictionary["userId"] as? Int
}
}
And use it like:
override func viewDidLoad() {
super.viewDidLoad()
// Initialize with data
Singleton.sharedInstance.initializeData(["completed": true, "id": 123, "title": "iOS Title", "userId": 572])
// Access data
let boolData = Singleton.sharedInstance.completed
print(boolData)
}

How to debug "Use of unresolved identifier" in Swift?

I'm learning about Xcode and using tutorials. I'm a beginner.
I'm looking at this code and have no clue why this is coming up with the following error:
Use of unresolved identifier 'USER_REF'
but in the code I'm pretty sure I reference the USER - is that right?
class DataService {
static let dataService = DataService()
fileprivate var _BASE_REF = Database.database().reference()
fileprivate var _USER_REF = Database.database().reference()
fileprivate var _USERPOST_REF = Database.database().reference()
var BASE_REF: DatabaseReference {
return _BASE_REF
}
var USER_REF: DatabaseReference {
return _USER_REF
}
var CURRENT_USER_REF: DatabaseReference {
let userID = UserDefaults.standard.value(forKey: "uid") as! String
let currentUser = Database.database().reference().child(byAppendingPath: "user").child(byAppendingPath: userID)
return currentUser
}
var USERPOST_REF: DatabaseReference {
return _USERPOST_REF
}
}
func createNewAccount(uid: String, user: Dictionary<String, String>) {
// A User is born?
USER_REF.child(byAppendingPath: uid).setValue(user)
}
func createNewUserPost(userpost: Dictionary<String, AnyObject>) {
// Save the Post
// USERPOST_REF is the parent of the new USERPOST: "userposts".
// childByAutoId() saves the userpost and gives it its own ID.
let firebaseNewUserPost = USERPOST_REF.childByAutoId()
// setValue() saves to Firebase.
firebaseNewUserPost?.setValue(userpost)
}
This is a perfect lesson in why indentation is important. There's a second } after the end of USERPOST_REF: DatabaseReference, which ends the scope of the class DataService.
As a result, createNewAccount(uid:user:) and createNewUserPost(userpost:) are standalone functions without access to any instance members of DatabaseReference.
A few points of improvement:
Swift convention for all properties, instance, class or static, is to use lowerCamelCase.
Unlike other languages (Java and Ruby are the main offenders here that I know of), there is no point making an instance variable with a public getter (computed property), such as filprivate _x: Int, with a public var x: Int. Namely, because _x is not an instance variable. Swift doesn't actually let you make instance variables. They're generated for you when you create properties. What you're doing is writing a property that accesses a property you declared, to access an instance variable the compiler synthesized. There's no need for this. Just make the property's setter scoped to file private.
The conventional name of a singleton in Swift is shared, default or main. Try to stick with one of those. Additionally, in order for you to truly have a singleton, you need to restrict access to the initializer, by declaring it with private access.
Here is what I would recommend:
class DataService {
static let shared = DataService()
private init() {}
public fileprivate(set) var baseDB = Database.database().reference()
public fileprivate(set) var userDB = Database.database().reference()
public fileprivate(set) var userPostDB = Database.database().reference()
var currentUser: DatabaseReference {
let userID = UserDefaults.standard.value(forKey: "uid") as! String
return Database.database()
.reference()
.child(byAppendingPath: "user")
.child(byAppendingPath: userID)
}
func createNewAccount(uid: String, user: Dictionary<String, String>) {
// A User is born?
userDB.child(byAppendingPath: uid).setValue(user)
}
func createNewUserPost(userpost: Dictionary<String, AnyObject>) {
// Save the Post
// userPostDB is the parent of the new USERPOST: "userposts".
// childByAutoId() saves the userpost and gives it its own ID.
let firebaseNewUserPost = userPostDB.childByAutoId()
// setValue() saves to Firebase.
firebaseNewUserPost?.setValue(userpost)
}
}

iOS: Custom object's int variable returns nil in NSUserDefaults

I have this weird issue. I'm saving custom class object in NSUserDefaults, and while retrieving the data I get nil for int variable of the object. Below is the custom class
class User {
var name: String?
var user_id: Int?
var account_id: Int?
var location: String?
}
I'm saving the object as,
let defaults = NSUserDefaults.standardUserDefaults()
var data = NSKeyedArchiver.archivedDataWithRootObject([user]) // I can see the int values for the user objects here
defaults.setObject(data, forKey: "all_users")
Retrieving the data as,
let defaults = NSUserDefaults.standardUserDefaults()
let data = defaults.dataForKey("all_users")
var users = [Users]()
if data != nil {
let userData = NSKeyedUnarchiver.unarchiveObjectWithData(data!) as! [Users]
for usr in userData {
print("\(usr.name!)") // Prints the name
print("\(usr.user_id!)") // Nil value here
users.append(usr)
}
}
I have absolutely no idea about the reason for this behavior.
Custom classes that have none property list items need to conform to NSCoding to be able to be saved in NSUserDefaults.
Here is a guide to conforming to NSCoding: http://nshipster.com/nscoding/
You will need both of these functions:
init(coder decoder: NSCoder) {
self.name = decoder.decodeObjectForKey("name") as String
self.user_id = decoder.decodeIntegerForKey("user_id")
self.account_id = decoder.decodeIntegerForKey("account_id")
self.location = decoder.decodeObjectForKey("self.location") as String
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeInt(self.user_id, forKey: "user_id")
coder.encodeInt(account_id, forKey: "account_id")
coder.encodeObject(self.location, forKey: "location")
}

Saving custom NSObject in NSUserDefaults

I'm having a modal entity file as below,
import UIKit
class MyProfile: NSObject {
var userName : String = ""
func initWithDict(dict: NSMutableDictionary) {
self.userName = dict.objectForKey("username") as! String
}
}
Saving that entity by encoding as below,
let myDict: NSMutableDictionary = ["username": "abc"]
let myEntity:MyProfile = MyProfile()
myEntity.initWithDict(myDict)
let userDefaults = NSUserDefaults.standardUserDefaults()
let encodedData = NSKeyedArchiver.archivedDataWithRootObject(myEntity)
userDefaults.setObject(encodedData, forKey: "MyProfileEntity")
userDefaults.synchronize()
Getting that saved entity as below,
let myEntity:MyProfile = MyProfile()
let userDefaults = NSUserDefaults.standardUserDefaults()
guard let decodedNSData = userDefaults.objectForKey("MyProfileEntity") as? NSData,
myEntity = NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSData) as? MyProfile!
else {
print("Failed")
return
}
print(myEntity.userName)
It's not working, having crashes and lot of syntax errors, I'm new to swift,
It's showing some syntax errors like definition conflicts with previous value in the unarchiveObjectWithData line. If I fix that error, then at the time of getting the entity from userdefaults it's crashing.
can anyone suggest how can I resolve it?
To save custom object into user default, you must implement NSCoding protocol. Please replace your custom data model like this:
class MyProfile: NSObject,NSCoding {
var userName : String = ""
#objc required init(coder aDecoder:NSCoder){
self.userName = aDecoder.decodeObjectForKey("USER_NAME") as! String
}
#objc func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(self.userName, forKey: "USER_NAME")
}
init(dict: [String: String]) {
self.userName = dict["username"]!
}
override init() {
super.init()
}
}
Here is the code for saving and retrieving MyProfile object:
// Save profile
func saveProfile(profile: MyProfile){
let filename = NSHomeDirectory().stringByAppendingString("/Documents/profile.bin")
let data = NSKeyedArchiver.archivedDataWithRootObject(profile)
data.writeToFile(filename, atomically: true)
}
// Get profile
func getProfile() -> MyProfile?{
if let data = NSData(contentsOfFile: NSHomeDirectory().stringByAppendingString("/Documents/profile.bin")){
let unarchiveProfile = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! MyProfile
return unarchiveProfile
} else{
return nil
}
}
Now here is the code snippet how to use those method:
// Create profile object
let profile = MyProfile(dict: ["username" : "MOHAMMAD"])
// save profile
saveProfile(profile)
// retrieve profile
if let myProfile = getProfile(){
print(myProfile.userName)
}else{
print("Profile not found")
}
You can't do this:
let myEntity:MyProfile = MyProfile()
Then later on, do this:
myEntity = ...
When something is defined with 'let', you cannot change it.
Change to
var myEntity: MyProfile?
It is possible that
NSKeyedUnarchiver.unarchiveObjectWithData(decodedNSData)
is returning nil. You then proceed to force unwrapping by adding
as? MyProfile!
try changing this to
as? MyProfile
Then later, see if you got something back
if let myEntity = myEntity {
print(myEntity.userName)
}

iOS Creating an Object Best Practice

I've created a wizard for user's to sign up using my app, however, I'm having some doubts as to how I should store their information along the way.
I have a User model, which is filled out when users are pulled from the database, however, there are some required fields on this model that wouldn't be filled out if I were to use it as the object that is passed along as the user goes through the the wizard.
Here is my User model:
final class User: NSObject, ResponseObjectSerializable, ResponseCollectionSerializable {
let id: Int
var facebookUID: String?
var email: String
var firstName: String
var lastName: String
var phone: String?
var position: String?
var thumbnail: UIImage?
var timeCreated: CVDate
init?(response: NSHTTPURLResponse, var representation: AnyObject) {
if let dataRepresentation = ((representation as! NSDictionary).valueForKey("data") as? [String: AnyObject]) {
representation = dataRepresentation
}
self.id = representation.valueForKeyPath("id") as! Int
self.facebookUID = (representation.valueForKeyPath("facebook_UID") as? String)
self.email = (representation.valueForKeyPath("email") as? String) ?? ""
self.firstName = (representation.valueForKeyPath("first_name") as? String) ?? ""
self.lastName = (representation.valueForKeyPath("last_name") as? String) ?? ""
self.phone = (representation.valueForKeyPath("phone") as? String)
self.position = (representation.valueForKeyPath("position_name") as? String)
self.thumbnail = UIImage(named: "ThomasBaldwin")
if let timeCreated = representation.valueForKeyPath("time_created") as? String {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
if let date = formatter.dateFromString(timeCreated) {
self.timeCreated = CVDate(date: date)
} else {
self.timeCreated = CVDate(date: NSDate())
}
} else {
self.timeCreated = CVDate(date: NSDate())
}
}
static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [User] {
var users: [User] = []
if let dataRepresentation = ((representation as! NSDictionary).valueForKey("data") as? [NSDictionary]) {
if let dataRepresentation = dataRepresentation as? [[String: AnyObject]] {
for userRepresentation in dataRepresentation {
if let user = User(response: response, representation: userRepresentation) {
users.append(user)
}
}
}
}
return users
}
}
Notice the variables id and timeCreated. These are both generated when a new row is added to the Users table in the database, therefore, I wouldn't have values for those variables until the user is actually created.
Also, I would like to add some methods to the model, such as validateUser which will be a method that makes sure all the fields are filled out, and validateEmail which will be a method that makes sure the email is in proper syntax, and so on...
My question is, should I
A. just make those constants optional and add those methods to my current User model
B. make another model called CreateUserModel that only has variables for the information the user will be filling out and put the extra methods in there
UPDATE
I updated my User class to use a dictionary as the storage mechanism and it already looks a lot cleaner. However, the issue that comes to mind is, how will another programmer know which fields he can grab from the User model since I'm not individually storing them as variables anymore. Would they just have to check the DB and look at the structure of the table?
Here's my updated User class:
final class User: NSObject, ResponseObjectSerializable, ResponseCollectionSerializable {
var properties = NSDictionary()
init?(response: NSHTTPURLResponse, representation: AnyObject) {
if let dataRepresentation = ((representation as! NSDictionary).valueForKey("data") as? [String: AnyObject]) {
properties = dataRepresentation
}
properties = representation as! NSDictionary
}
static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [User] {
var users: [User] = []
if let dataRepresentation = ((representation as! NSDictionary).valueForKey("data") as? [NSDictionary]) {
if let dataRepresentation = dataRepresentation as? [[String: AnyObject]] {
for userRepresentation in dataRepresentation {
if let user = User(response: response, representation: userRepresentation) {
users.append(user)
}
}
}
}
return users
}
}
I would make them Optionals. That is the beauty of Optionals - you can use nil to mean exactly "no data here".
The other grand strategy that comes to mind is to use a dictionary as the storage mechanism inside your model, because that way either it has a certain key or it doesn't. You could make your User object key-value coding compliant, and thus effectively transparent, by passing keys on to the dictionary.

Resources