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.
Related
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
})
}
}
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)
}
I am attempting to use the Realm library to persist data within my application. However, I keep running into the same error code: "Realm accessed from incorrect thread". I attempted to resolve this issue by creating a Realm-specific Dispatch Queue, and wrapping all of my Realm calls in it.
Here is what my "RealmManager" class looks like right now:
import Foundation
import RealmSwift
class RealmManager {
fileprivate static let Instance : RealmManager = RealmManager()
fileprivate var _realmDB : Realm!
fileprivate var _realmQueue : DispatchQueue!
class func RealmQueue() -> DispatchQueue {
return Instance._realmQueue
}
class func Setup() {
Instance._realmQueue = DispatchQueue(label: "realm")
Instance._realmQueue.async {
do {
Instance._realmDB = try Realm()
} catch {
print("Error connecting to Realm DB")
}
}
}
class func saveObjectArray(_ objects: [Object]) {
Instance._realmQueue.async {
do {
try Instance._realmDB.write {
for obj in objects {
Instance._realmDB.add(obj, update: .all)
}
}
} catch {
print("Error Saving Objects")
}
}
}
class func fetch(_ type: Int) -> [Object] {
if let realm = Instance._realmDB {
let results = realm.objects(Squeak.self).filter("type = \(type)")
var returnArray : [Object] = []
for r in results {
returnArray.append(r)
}
return returnArray
}
return []
}
I am calling Setup() inside of didFinishLaunchingWithOptions to instantiate the Realm queue and Realm Db instance.
I am getting the error code inside of saveObjectArray at:
try Instance._realmDB.write { }
This seems to simply be a matter of my misunderstanding of the threading requirements of Realm. I would appreciate any insight into the matter, or a direction to go in from here.
This issue is that you fetch your Realm data on a different thread than you save it.
To fix the error, the code within fetch will also need to run on the Realm thread that you have created.
I think this article does a good job of explaining multi-threading in Realm and particularly recommend paying attention to the three rules outlined in the article.
I have a tracking app working in the background that uses Realm for persistency. I noticed the problem that sometimes the received locations are not saved in Realm an I think this could happen because of multithreading.
Here is my architecture:
The LocationLogger has an instance of CLLocationManager and an instance of my class for persistency: LocationModel. LocationManager is of type BaseModel and this one has an instance of Realm. In BaseModel realm loads an instance of Realm from my class RealmProvider:
lazy var realm: Realm = {
return RealmProvider().loadRealm()
}()!
And this code of RealmProvider is this:
class RealmProvider {
private var realm: Realm?
private let currentSchemaVersion: UInt64 = 8
func loadRealm() -> Realm? {
if let realm = self.realm {
return realm
}
do {
if let _ = NSClassFromString("XCTest"){
realm = try Realm(configuration: Realm.Configuration(fileURL: nil, inMemoryIdentifier: "test", syncConfiguration: nil, encryptionKey: nil, readOnly: false, schemaVersion: currentSchemaVersion, migrationBlock: nil, deleteRealmIfMigrationNeeded: true, objectTypes: nil))
} else {
realm = try Realm(configuration: Realm.Configuration(encryptionKey: nil, readOnly: false, schemaVersion: currentSchemaVersion, migrationBlock: nil, deleteRealmIfMigrationNeeded: true, objectTypes: nil))
}
}
catch {
logger.error("eror loading Realm!")
}
return realm
}
}
The reason why I have a RealmProvider is to have the configuration like versioning of the schema in one place.
Can you imagine why this isn't working always? Perhaps when LocationLogger is created in Thread A und the location callback comes in thread B? Other ideas?
Is it better to create an instance of Realm everytime a new location is reported? How can I do the schema configuration then?
Suggestion for the final solution:
class RealmProvider {
static private let currentSchemaVersion: UInt64 = 8
private lazy var configuration: Realm.Configuration = {
if let _ = NSClassFromString("XCTest") {
return Realm.Configuration(inMemoryIdentifier: "test", schemaVersion: currentSchemaVersion, deleteRealmIfMigrationNeeded: true)
}
return Realm.Configuration(schemaVersion: currentSchemaVersion, deleteRealmIfMigrationNeeded: true)
}()
public var realm: Realm {
var tempRealm: Realm?
do {
tempRealm = try Realm(configuration: configuration)
}
catch {
logger.error("eror loading Realm!")
}
if let tempRealm = tempRealm{
return tempRealm
}
return self.realm
}
}
Yes. It is better to create instances of Realm on demand rather than pre-emptively creating an instance ahead of time and retaining it. Since Realm objects are thread-confined, creating a Realm on Thread A, and then trying to interact with it on Thread B will cause an exception.
When calling Realm(configuration:), Realm itself will internally cache that object and will return the same object on each subsequent call. The object remains in the cache until the autoreleasepool it was created in is drained.
Like I said above, Realm objects are thread-confined. It's necessary to create a new instance of Realm when running on a new thread. Trying to pass an instance of a Realm from another thread will trigger an exception. This is the main reason why it's recommended not to retain an instance of Realm, especially if background operations are present.
Realm Configuration objects, as long as they aren't modified after their creation are thread-safe. So it's completely reasonable to create and retain a Configuration instance, and then re-use this instance to create Realm instances on any subsequent thread.
So to modify your above example:
class RealmProvider {
private let currentSchemaVersion: UInt64 = 8
private lazy var configuration: Configuration = {
if let _ = NSClassFromString("XCTest") {
return Realm.Configuration(inMemoryIdentifier: "test", schemaVersion: currentSchemaVersion,deleteRealmIfMigrationNeeded: true)
}
return Realm.Configuration(schemaVersion: currentSchemaVersion, deleteRealmIfMigrationNeeded: true)
}()
public var realm: Realm? {
var realm: Realm?
do {
realm = try Realm(configuration: configuration)
}
catch {
logger.error("eror loading Realm!")
}
return realm
}
}
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.