How to globally store a User Class instance in Swift - swift

I have been coding in swift for a short time now and wish to create my first, properly complete application. My application starts with a UITabController (after the logging in part which I have implemented) will come with a "profile" page, where the user can update information about themselves (username etc).
I have therefore created a User class which holds this information and will in the future, communicate with a server to update the users information.
I only want one User class object to be instantiated throughout the application (yet still accessible everywhere) as only one user can be logged in on the phone, what is considered the best practice to do so? It may also be worth noting that the log in section will remember a user is logged in so they won't have to re-log in (using user defaults Boolean for isLoggedIn)
I was thinking about making the User class as a singleton, or somehow making the class instance global (although I am pretty sure making it global isn't great).
Or is there a way to make the instance accessible for every view controller placed in a UITabController class if I create the User class in the tab controller class? What do you recommend?
Thanks all!

This is how I use a single object for user data that both is available in any view controller I want but also allows for the saving of data. This utilizes Realm for swift for saving data. To call the data you just create the variable let user = User.getCurrentUser()
class User: Object, Decodable {
#objc dynamic var firstName: String? = ""
#objc dynamic var lastName: String? = ""
#objc dynamic var email: String = ""
#objc dynamic var signedIn: Bool = false
override static func primaryKey() -> String? {
return "email"
}
private enum CodingKeys: String, CodingKey {
case email
case firstName
case lastName
}
}
extension User {
func updateUser(block: (User) -> ()) {
let realm = UserRealm.create()
try? realm.write {
block(self)
}
static func getCurrentUser() -> User? {
let realm = UserRealm.create()
return realm.objects(User.self).filter("signedIn == %#", true).first
}
}
fileprivate let currentSchema: UInt64 = 104
struct UserRealm {
static func create() -> Realm {
do {
return try Realm(configuration: config)
} catch {
print(error)
fatalError("Creating User Realm Failed")
}
}
static var config: Realm.Configuration {
let url = Realm.Configuration().fileURL!.deletingLastPathComponent().appendingPathComponent("Users.realm")
return Realm.Configuration(fileURL: url,
schemaVersion: currentSchema, migrationBlock: { migration, oldSchema in
print("Old Schema version =", oldSchema)
print("Current schema version =", currentSchema)
print("")
if oldSchema < currentSchema {
}
}, shouldCompactOnLaunch: { (totalBytes, usedBytes) -> Bool in
let oneHundredMB = 100 * 1024 * 1024
return (totalBytes > oneHundredMB) && (Double(usedBytes) / Double(totalBytes)) < 0.5
})
}
}

Related

Updating Struct variables on a singleton instance

I am trying to update a locally stored feature flag values on my singleton instance based on the UI changes and having trouble implementing a solution. Any help here is much appreciated.
The following is the singleton class that gets instantiated during app launch and the Featureflag values are read from remote and is cached in the "cachedKillSwitchValues" attribute. I have also detailed the implementation of the "KillSwitchValuesCacheEntry" and the "KillSwitches" struct as well.
The feature flag values are displayed in the debug screen of the app so we can override the remote values by toggling them. When this happens I have to override the cachedKillSwitchValues to retain the changes.
On the UI I convert the KillSwitches() object into "var tableData: [(String, Bool)] = []" so it becomes easier to load the table View data . The challenge is I am confused on how to update the cachedKillSwitchValues since my updateLocalKillSwitchCache(key: String, value: Bool) requires a string and a bool argument while I see the only way to update the KillSwitches are by accessing the attribute directly. I took a shot at coding the update method but couldn't complete.
public final class AppConfigurationRepository {
public static let shared = AppConfigurationRepository()
private var cachedKillSwitchValues: KillSwitchValuesCacheEntry?
private init() {
cachedKillSwitchValues = KillSwitchValuesCacheEntry(entryItem: KillSwitches(), lastUpdateMetaDate: Date())
}
public func updateLocalKillSwitchCache(key: String, value: Bool) {
// The code below is wrong, tried implementing multiple ways but went no where.
var killSwithDict = cachedKillSwitchValues?.entryData.dictionary()
killSwithDict?.updateValue(value, forKey: key)
cachedKillSwitchValues?.entryData = killSwithDict as KillSwitches
}
}
struct KillSwitchValuesCacheEntry: Codable {
var entryData: KillSwitches
let remoteConfigLastUpdateDate: Date
init(entryItem: KillSwitches, lastUpdateMetaDate: Date) {
self.entryData = entryItem
self.remoteConfigLastUpdateDate = lastUpdateMetaDate
}
}
public struct KillSwitches: Codable {
public var featureA: Bool = true
public var featureB: Bool = true
public enum CodingKeys: String, CodingKey {
case featureA
case featureB
}
init() { }
}

Updating a codable user struct

I'm developing an app where the user can create up to 5 profiles when I came across a problem.
Problem:
Fatal error: Unexpectedly found nil while unwrapping an Optional value: file
Info:
The first edition of the app has a struct with these data points in it:
id
profileName
profileIcon
When the app opens it loads the user via func loadUser()
Now, during an update I've added a new data point in the user struct so it now looks like this:
id
profileName
profileIcon
profileSummary
Now when the func loadUser() is called it fails with this statement:
Fatal error: Unexpectedly found nil while unwrapping an Optional value: file
How the system is built:
When the app is opened the first time it creates 5 empty profiles. The user can then "activate" and fill out these profiles and he/she likes.
I'm a little uncertain how to deal with this problem. How can I add new data points to my struct without causing the app to crash?
Source code:
struct User: Codable {
// Core user data
let id: Int
var profileName: String
var profileIcon: String
var profileSummary: String
}
class DataManager: NSObject {
/// Used to encode and save user to UserDefaults
func saveUser(_ user: User) {
if let encoded = try? JSONEncoder().encode(user) {
UserDefaults.standard.set(encoded, forKey: "userProfile_\(user.id)")
print("Saved user (ID: \(user.id)) successfully.")
}
}
/// Used to decode and load user from UserDefaults
func loadUser(_ userID: Int) -> User {
var user : User?
if let userData = UserDefaults.standard.data(forKey: "userProfile_\(userID)"),
let userFile = try? JSONDecoder().decode(User.self, from: userData) {
user = userFile
print("Loaded user (ID: \(user!.id)) successfully.")
}
return user!
}
/// Used to create x empty userprofiles ready to be used
func createEmptyProfiles() {
// May be changed, but remember to adjust UI
var profilesAllowed = 5
while profilesAllowed != 0 {
print("Attempting to create empty profile for user with ID \(profilesAllowed)")
let user = User(id: profilesAllowed, profileName: "", profileIcon: "", profileSummary: "Write a bit about your profile here..")
self.saveUser(user)
print("User with ID \(profilesAllowed) was created and saved successfully")
// Substract one
profilesAllowed -= 1
}
}
//MARK: - Delete profile
func deleteUser(user: User) -> Bool {
var userHasBeenDeleted = false
var userToDelete = user
// Reset all values
userToDelete.profileName = ""
userToDelete.profileIcon = ""
userToDelete.profileSummary = ""
// Save the resetted user
if let encoded = try? JSONEncoder().encode(userToDelete) {
UserDefaults.standard.set(encoded, forKey: "userProfile_\(user.id)")
print("User has now been deleted")
userHasBeenDeleted = true
}
return userHasBeenDeleted
}
}
Two options:
Make profileSummary optional
struct User: Codable {
// Core user data
let id: Int
var profileName: String
var profileIcon: String
var profileSummary: String?
}
If the key doesn't exist it will be ignored.
Implement init(from decoder) and decode profileSummary with decodeIfPresent assigning an empty string if it isn't.
Side note:
Never try? in a Codable context. Catch the error and handle it. Your loadUser method crashes reliably if an error occurs. A safe way is to make the method throw and hand over errors to the caller.
enum ProfileError : Error { case profileNotAvailable }
/// Used to decode and load user from UserDefaults
func loadUser(_ userID: Int) throws -> User {
guard let userData = UserDefaults.standard.data(forKey: "userProfile_\(userID)") else { throw ProfileError.profileNotAvailable }
return try JSONDecoder().decode(User.self, from: userData)
}

Performance issues on Realm List

I'm having some memory performance issues when doing operations on Realm List's. I have two objects similar to this one:
final class Contact: Object {
let phones = List<Phone>()
let emails = List<Email>()
}
Now I'm trying to find possible similarities between two objects of the same type (e.g. at least one element in common) that could potentially have duplicate emails or phones. In order to do so I was using Set operations.
func possibleDuplicateOf(contact: Contact) {
return !Set(emails).isDisjoint(with: Set(contact.emails)) || !Set(phones).isDisjoint(with: Set(contact.phones))
}
This is a function inside the Contact object. I know it has a performance hit when transforming Realm List into a Set or an Array, and I'm feeling this heavily when I have a large amount of Contacts (10k or more), memory consumption jumps to more then 1GB.
So I tried replacing the above function with this one:
func possibleDuplicateOf(contact: Contact) {
let emailsInCommon = emails.contains(where: contact.emails.contains)
let phonesInCommon = phones.contains(where: contact.phones.contains)
return emailsInCommon || phonesInCommon
}
This has the same performance has using the sets.
The isEqual method on the Emails and Phones is a simple string comparision:
extension Email {
static func ==(lhs: Email, rhs: Email) -> Bool {
return (lhs.email == rhs.email)
}
override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? Email else { return false }
return object == self
}
override var hash: Int {
return email.hashValue
}
}
Email.swift
final class Email: Object {
enum Attribute: String { case primary, secondary }
#objc dynamic var email: String = ""
#objc dynamic var label: String = ""
/* Cloud Properties */
#objc dynamic var attribute_raw: String = ""
var attribute: Attribute {
get {
guard let attributeEnum = Attribute(rawValue: attribute_raw) else { return .primary }
return attributeEnum
}
set { attribute_raw = newValue.rawValue }
}
override static func ignoredProperties() -> [String] {
return ["attribute"]
}
convenience init(email: String, label: String = "email", attribute: Attribute) {
self.init()
self.email = email
self.label = label
self.attribute = attribute
}
}
I'm a bit out of options in here, I've spent the entire day trying to come up with a different approach to this problem but without any luck. If anyone has a better idea, I would love to hear it out :)
Thank you
Whenever something like this happens, a good start is using Instruments to find out where CPU cycles and memory are consumed. Here's a good tutorial: Using Time Profiler in Instruments
You omitted the code making the actual comparison, but I suspect it might be nested for loops or something along those lines. Realm doesn't know your use case and isn't caching appropriately for something like that.
Using Instruments, it's fairly easy to find the bottlenecks. In your case, this should work:
final class Contact: Object
{
let emails = List<Email>()
lazy var emailsForDuplicateCheck:Set<Email> = Set(emails)
func possibleDuplicateOf(other: Contact) -> Bool {
return !emailsForDuplicateCheck.isDisjoint(with: other.emailsForDuplicateCheck)
}
override static func ignoredProperties() -> [String] {
return ["emailsForDuplicateCheck"]
}
}
And for the comparison:
// create an array of the contacts to be compared to cache them
let contacts = Array(realm.objects(Contact.self))
for contact in contacts {
for other in contacts {
if contact.possibleDuplicateOf(other: other) {
print("Possible duplicate found!")
}
}
}
This implementation ensures that the Contact objects are fetched only once and the Set of Email is only created once for each Contact.
The problem you have might be solved more optimal by rebuilding a bit data structures. Getting everything in memory and trying to convert to set (building sets is expensive operation) is far from optimal :(. I suggest this solution.
Consider contact is this object (I've added id property). I didn't add phones objects for brevity but exactly the same approach may be used for phones.
class Contact: Object {
#objc dynamic var id = UUID().uuidString
var emails = List<Email>()
override public static func primaryKey() -> String? {
return "id"
}
}
And Email class is this one. Relation to contact is added.
class Email: Object {
#objc dynamic var email: String = ""
#objc dynamic var contact: Contact?
}
Having these "connected" tables in realm you may create query to find duplicated objects:
func hasDups(contact: Contact) -> Bool {
let realm = try! Realm()
let emails: [String] = contact.emails.map { $0.email }
let sameObjects = realm.objects(Email.self)
.filter("email in %# AND contact.id != %#", emails, contact.id)
// sameObject will contain emails which has duplicates with current contact
return !sameObjects.isEmpty
}
This works super fast, I've tested on 100000+ objects and executed immediately.
Hope this helps!

Realm Server Object Incorrect Thread Exception

I had an app were realm objects were managed locally like this
import Foundation
import RealmSwift
class Patient: Object {
static var realm: Realm?
dynamic var name = ""
convenience init(name: String, save: Bool = false) {
self.init()
self.name = name
if save() {
self.save
}
}
func save() {
try! Patient.realm?.write {
Patient.realm?.add(self, update: true)
}
}
static func getAllPatients() -> Results<Patient>? {
return Patient.realm?.objects(Patient.self)
}
}
When I tried to convert the above code to sync with Realm Object Server, I got thread error trying to pass the realm instance passed from the login method to my class
static func userLogin(onCompletion: #escaping (Realm) -> Void) {
let serverURL = URL(string: "http://127.0.0.1:9080")!
let credentials = SyncCredentials.usernamePassword(username: "test#test", password: "test")
SyncUser.logIn(with: credentials, server: serverURL) {
user, error in
if let user = user {
let syncServerURL = URL(string: "realm://localhost:9080/~/test")!
let config = Realm.Configuration(syncConfiguration: SyncConfiguration(user: user, realmURL: syncServerURL))
let realm = try! Realm(configuration: config)
onCompletion(realm)
} else if _ = error {
}
}
}
and here to get the realm instance
userLogin() { realm in
Patient.realm = realm
}
Now, when I use this new Patient.realm in my class functions (getAllPatients), I get incorrect thread exception
Any possible way to pass the realm instance from userLogin to my class without causing this thread exception? If I put my queries in the login function, does that mean I need to login, sync everytime I need to get something from or edit the database?
In your login function you should be storing the Realm.Configuration object created with the logged-in user and using that to create a Realm instance as needed rather than trying to store the Realm object. Realm instances are thread-specific, while config objects are not.

How to save a struct to realm in swift?

It is easy to use Realm with classes by inheriting from Object. But how would I save a struct containing several fields to realm in Swift? E.g.
struct DataModel {
var id = 0
var test = "test"
}
I know the documentation is clear about supported types. But maybe there is nice workaround or - even better - someone from realm could write about future plans about structs.
I' suggest you to use protocols, to achive what you want.
1) Create your Struct
struct Character {
public let identifier: Int
public let name: String
public let realName: String
}
2) Create your Realm Object
final class CharacterObject: Object {
dynamic var identifier = 0
dynamic var name = ""
dynamic var realName = ""
override static func primaryKey() -> String? {
return "identifier"
}
}
3) Use protocols to transform our struct to Realm Object
public protocol Persistable {
associatedtype ManagedObject: RealmSwift.Object
init(managedObject: ManagedObject)
func managedObject() -> ManagedObject
}
4) Make your struct persistable
extension Character: Persistable {
public init(managedObject: CharacterObject) {
identifier = managedObject.identifier
name = managedObject.name
realName = managedObject.realName
}
public func managedObject() -> CharacterObject {
let character = CharacterObject()
character.identifier = identifier
character.name = name
character.realName = realName
return character
}
}
With these tools in place, we are ready to implement the insertion methods of our persistence layer.
5) Exemple to write datas
public final class WriteTransaction {
private let realm: Realm
internal init(realm: Realm) {
self.realm = realm
}
public func add<T: Persistable>(_ value: T, update: Bool) {
realm.add(value.managedObject(), update: update)
}
}
// Implement the Container
public final class Container {
private let realm: Realm
public convenience init() throws {
try self.init(realm: Realm())
}
internal init(realm: Realm) {
self.realm = realm
}
public func write(_ block: (WriteTransaction) throws -> Void)
throws {
let transaction = WriteTransaction(realm: realm)
try realm.write {
try block(transaction)
}
}
}
5) Use the magic!
let character = Character(
identifier: 1000,
name: "Spiderman",
realName: "Peter Parker"
)
let container = try! Container()
try! container.write { transaction in
transaction.add(character)
}
Amazing source : Using Realm with Value Types & My Article
To save a struct in Realm, means copying the data into a Realm Object. The reason why Realm Objects are classes and not structs is because they are not inert values, but auto-updating objects that represent the persisted data in Realm. This has practical benefits, such as the fact that a Realm Object's data is lazy loaded.
You can take advantage of Realm's approach by responding to the change notifications from a Realm instance. For example if your UITableView data source is based off an array property on a Realm Object, as long as you have an instance of that object, you are guaranteed that after the notification it represents the correct values. Used properly this can simplify your code versus having multiple copies of values as structs.
Swift 4 shortest answer
Save structs as Data in Realm
struct MyStruct : Codable { // Variables here }
class MyRealObject : Object {
#objc private dynamic var structData:Data? = nil
var myStruct : MyStruct? {
get {
if let data = structData {
return try? JSONDecoder().decode(MyStruct.self, from: data)
}
return nil
}
set {
structData = try? JSONEncoder().encode(newValue)
}
}
}
Use the magic
let realm = try! Realm()
try! realm.write {
let myReal = MyRealObject()
myReal.myStruct = MyStruct(....)
realm.add(myReal)
}
You can do what suggests Ludovic, or you can automate that process and get rid of that boilerplate code for each of your structs by using Unrealm.