Why Does Realm use try! so frequently? It seems like if you're certain your call won't fail then you should not design it to throw - no?
Here is an example, from the Swift page on realm.io:
// Get the default Realm
let realm = try! Realm()
or
// Persist your data easily
try! realm.write {
realm.add(myDog)
}
To me this implies they will never fail so why have the constructor or write() throw?
If you're referring to the examples in the Realm Swift Docs, I suspect try! is used liberally for the sake of brevity. The user is given a quick and dirty overview of core concepts without too much mental overhead.
You probably will encounter errors at some point in your journey using Realm. You'll notice later on in the docs, in the Realms > Error Handling section that a do-catch example is given.
do {
let realm = try Realm()
} catch let error as NSError {
// handle error
}
To me, it's implied that the code examples from the docs are not necessarily production-quality, and the user is encouraged to use the relevant error-handling features of Swift.
From the Realm Swift 2.1.0 guide in the Writes section:
Because write transactions could potentially fail like any other disk
IO operations, both Realm.write() & Realm.commitWrite() are marked as
throws so you can handle and recover from failures like running out of
disk space. There are no other recoverable errors. For brevity, our
code samples don’t handle these errors but you certainly should in
your production applications.
Source: https://realm.io/docs/swift/latest/#writes
The way I deal with this issue is by creating a DatabaseManager class, that handles the unlikely event of realm throwing an error:
public class DatabaseManager {
static var realm: Realm {
get {
do {
let realm = try Realm()
return realm
}
catch {
NSLog("Could not access database: ", error)
}
return self.realm
}
}
public static func write(realm: Realm, writeClosure: () -> ()) {
do {
try realm.write {
writeClosure()
}
} catch {
NSLog("Could not write to database: ", error)
}
}
}
Thanks to that solution the code looks much cleaner whenever I want to read from realm or write to db :)
DatabaseManager.write(realm: realm) {
let queryResult = self.realm.objects(Cookies.self).filter("cookieId == %#", cookieId)
let cookie = queryResult.first
cookie?.expirationDate = expirationDate as NSDate?
}
Why creating a class with static func when we can create an extension of Realm?
extension Realm {
static func safeInit() -> Realm? {
do {
let realm = try Realm()
return realm
}
catch {
// LOG ERROR
}
return nil
}
func safeWrite(_ block: () -> ()) {
do {
// Async safety, to prevent "Realm already in a write transaction" Exceptions
if !isInWriteTransaction {
try write(block)
}
} catch {
// LOG ERROR
}
}
}
Usage Example
Old unsafe code:
let realm = try! Realm()
try! realm.write {
// Your write transaction body
}
Safety refactor with this extension:
guard let realm = Realm.safeInit() else {
// Track Error
return
}
realm.safeWrite {
// Your write transaction body as before
}
From the Realm documentation:
You may have noticed so far that we have initialized access to our realm variable by calling Realm(). That method returns a Realm object that maps to a file called “default.realm” under the Documents folder (iOS) or Application Support folder (OS X) of your app.
Any time you interact with the file system you risk encountering errors such as permissions problems or insufficient disk space. Success is not certain.
So if for any reason Realm is unable to create or write to the realm file, these methods you cite would indeed throw an exception.
I create this for simple init call
import RealmSwift
// MARK: - RealmDB
/// RealmDB import realm in foundation, and add is format for refactoring catch
public class RealmDB {
/// Realm
public static var realm: Realm? {
do {
return try Realm()
} catch let error {
NotificationCenter.default.post(name: .logError, object: "Could not access database: \(error)")
return nil
}
}
/// Write in Realm
///
/// - Parameter writeClosure: Write Closure
public static func write(writeClosure: #escaping (_ realm: Realm) -> ()) {
do {
try self.realm?.write {
// self.realm has so can `!`
writeClosure(self.realm!)
}
} catch let error {
NotificationCenter.default.post(name: .logError, object: "Could not write database: \(error)")
}
}
}
Related
NOTE: I have seen other posts but my problem is a little different
I have a helper class to access Realm. Every function in this class creates it's own instance of the Realm object to avoid thread issues, to be specific Realm accessed from incorrect thread.; This works perfectly fine for disk Realm; however, for my in memory realm the data is inserted successfully but when I try to retrieve it I get nothing. I thought maybe Realm is being accessed from different threads so what I did is I created a DispatchQueue and I always access realm from that queue.
Here is my code
protocol Cachable {}
protocol InMemoryCache {
func create<T: Cachable>(model: T.Type,
_ completion: #escaping (Result<T, Error>) -> ())
func save(object: Cachable,
_ completion: #escaping (Result<Void, Error>) -> ())
func fetch<T: Cachable>(model: T.Type,
predicate: NSPredicate?,
sorted: Sorted?,
_ completion: #escaping (Result<[T], Error>) -> ())
}
enum RealmInMemoryCacheError: Error {
case notRealmSpecificModel
case realmIsNil
case realmError
}
final class RealmInMemoryCache {
private let configuration: Realm.Configuration
private let queue: DispatchQueue
init(_ configuration: Realm.Configuration) {
self.queue = DispatchQueue(label: "inMemoryRealm", qos: .utility)
self.configuration = configuration
}
}
extension RealmInMemoryCache : InMemoryCache{
func create<T>(model: T.Type,
_ completion: #escaping (Result<T, Error>) -> ()) where T : Cachable {
self.queue.async {
guard let realm = try? Realm(configuration: self.configuration) else {
return completion(.failure(RealmInMemoryCacheError.realmIsNil))
}
guard let model = model as? RealmSwift.Object.Type else {
return completion(.failure(RealmInMemoryCacheError.notRealmSpecificModel))
}
do {
try realm.write { () -> () in
let newObject = realm.create(model, value: [], update: .all) as! T
return completion(.success(newObject))
}
} catch {
return completion(.failure(RealmInMemoryCacheError.realmError))
}
}
}
func save(object: Cachable,
_ completion: #escaping (Result<Void, Error>) -> ()) {
self.queue.async {
guard let realm = try? Realm(configuration: self.configuration) else {
return completion(.failure(RealmInMemoryCacheError.realmIsNil))
}
guard let object = object as? RealmSwift.Object else {
return completion(.failure(RealmInMemoryCacheError.notRealmSpecificModel))
}
do {
try realm.write { () -> () in
realm.add(object, update: .all)
return completion(.success(()))
}
} catch {
return completion(.failure(RealmInMemoryCacheError.realmError))
}
}
}
func fetch<T>(model: T.Type,
predicate: NSPredicate?,
sorted: Sorted?,
_ completion: #escaping (Result<[T], Error>) -> ()) where T : Cachable {
self.queue.async {
guard let realm = try? Realm(configuration: self.configuration) else {
return completion(.failure(RealmInMemoryCacheError.realmIsNil))
}
guard
let model = model as? RealmSwift.Object.Type else {
return completion(.failure(RealmInMemoryCacheError.notRealmSpecificModel))
}
var objects = realm.objects(model)
if let predicate = predicate {
objects = objects.filter(predicate)
}
if let sorted = sorted {
objects = objects.sorted(byKeyPath: sorted.key, ascending: sorted.ascending)
}
return completion(.success(objects.compactMap { $0 as? T}))
}
}
}
extension Object: Cachable {}
struct Sorted {
var key: String
var ascending: Bool = true
}
I eliminated code that doesn't add any benefit to the question hence you see empty/missing things in the above code. However, the code above works 100% copied and pasted.
I tried creating realm in the initialized instead so I have a strong reference to it; however, that causes issues with thread safety, it may work for few times but it would at some point crash the app due to the error Realm accessed from incorrect thread.
As you may tell, my goal is to make the above code generic and 100% thread safe even if called from a background thread say in a different function. Reason behind it is imagine the above class is an API and different programmers will use it, and sometimes they will call a function on a background thread for example without actually knowing what is going under the hood. I do not want the application to crash if they did such a thing.
EDIT: This is how the helper class is initialized
let realmInMemory = RealmInMemoryCache(Realm.Configuration(inMemoryIdentifier: "globalInMemoryRealm")
// Then I can use it like so (replace model with your realm model)
realmInMemory.create(model) { result in {
switch result {
...
}
}
EDIT 2: Here is a full example of how the above class works
import RealmSwift
final class MessageRealmEntity: Object {
#objc dynamic var id: String = ""
#objc dynamic var message: String = ""
convenience init(id: String, message: String) {
self.init()
self.id = id
self.message = message
}
override static func primaryKey() -> String? {
"id"
}
}
// THIS IS NOT PART OF THE PROBLEM, THIS `Main` CLASS IS JUST A DRIVER. THE CODE INSIDE IT COULD RUN ANYWHERE.
final class Main {
let realmInMemory = RealmInMemoryCache(Realm.Configuration(inMemoryIdentifier: "globalInMemoryRealm"))
func run() {
DispatchQueue.global(qos: .background).async {
let semaphore: DispatchSemaphore = DispatchSemaphore(value: 0)
var entity = MessageRealmEntity(id: "1", message: "Hello, World!")
self.realmInMemory.save(object: entity) { result in
switch result {
case .success(_):
print("Saved successfully")
case .failure(let error):
print("Got error")
}
semaphore.signal()
}
_ = semaphore.wait(wallTimeout: .distantFuture)
self.realmInMemory.fetch(model: MessageRealmEntity.self, predicate: nil, sorted: nil) { result in
switch result {
case .success(let messages):
print(messages.count) // This will return 0 when it should be 1 since we inserted already
case .failure(let error):
print("Got error")
}
semaphore.signal()
}
_ = semaphore.wait(wallTimeout: .distantFuture)
}
}
}
let main: Main = Main()
main.run()
All other methods are called the same way.
EDIT3:
I opened github issue if anyone is interested to follow it, here is the link: https://github.com/realm/realm-cocoa/issues/7017 there is a video and more explanation there
This is a github link to download a project to reproduce the bug https://github.com/Muhand/InMemoryRealm-Bug
After reading the documentation of Realm again and again and keep thinking of what #Jay have said in the comments, I paid more attention to this quote from the documentation
Notice: When all in-memory Realm instances with a particular identifier go out of scope with no references, all data in that Realm is deleted. We recommend holding onto a strong reference to any in-memory Realms during your app’s lifetime. (This is not necessary for on-disk Realms.)
Key sentence in the above quote is
When all in-memory Realm instances with a particular identifier go out of scope with no references, all data in that Realm is deleted.
In other words my Realm object is going out of scope everytime I try to save or fetch or do any other function
for example:
self.queue.async {
guard let realm = try? Realm(configuration: self.configuration) else {
completion(.failure(RealmInMemoryCacheError.realmIsNil))
return
}
guard let object = object as? RealmSwift.Object else {
completion(.failure(RealmInMemoryCacheError.notRealmSpecificModel))
return
}
do {
try realm.write { () -> () in
realm.add(object, update: .all)
completion(.success(()))
return
}
} catch {
completion(.failure(RealmInMemoryCacheError.realmError))
return
}
}
In the above code Realm goes out of scope as soon as that queue is done with it. Then Realm will look if there is any other variable in memory with the same identifier if so then it does nothing otherwise it will delete the current realm to optimize.
So the solution to the problem is basically to create a strong reference to realm with this identifier and then in each function re-create realm with the same identifier as well to avoid Realm accessed from incorrect thread This will however render in an extra variable that is not used but I think that is okay for now, at least it is my solution until something official comes from Realm. Keep in mind the process of re-initializing should not be an overhead since Realm takes care of that optimization.
Here is what I have done
final class RealmInMemoryCache {
private let configuration: Realm.Configuration
private let queue: DispatchQueue
private let strongRealm: Realm <-- Added variable
init(_ configuration: Realm.Configuration) {
self.queue = DispatchQueue(label: "inMemoryRealm", qos: .utility)
self.configuration = configuration
self.strongRealm = try! Realm(configuration: self.configuration) <-- Initialized here
}
}
and in other functions I do something like the following
...
self.queue.async {
guard let realm = try? Realm(configuration: self.configuration) else {
completion(.failure(RealmInMemoryCacheError.realmIsNil))
return
}
...
What threw me off thinking my Realm was going out of scope is when I setup breakpoints Realm started behaving perfectly fine. Although I still don't know 100% for sure why but my thinking is that xcode debugger might have created a strong reference to Realm when I write the command po realm.objects(...) in lldb.
I will accept this answer for now, unless someone has a better solution.
I'm using a function to save a realm object to my server. It's called using DataManager().updatedCategory(~InsertCategory~). The implementation goes like this;
func updatedCategory(_ c: Category) {
guard let realm = try? Realm() else { return }
guard let category = realm.object(ofType: Category.self, forPrimaryKey: c.id) else { return }
do {
try realm.write {
c.updatedAt = Date()
}
} catch {
print(error)
}
dbCategories.document(category.id).updateData(category.dictionaryValue())
}
This function is being called after using a realm.write block. It's not in another write block and even the UI is being updated before this is called. I can't figure out what's causing this error, since there's no notification being registered.
I think this
try realm.write {
c.updatedAt = Date()
}
should be
try realm.write {
category.updatedAt = Date()
}
also, this
dbCategories.document(category.id).updateData(category.dictionaryValue())
looks like you're also attempting to write data to realm (?). If so, it needs to be within a write closure. If not, clarify what that does so I can update the answer.
Lastly, this is a bit unclear
This function is being called after using a realm.write block.
You can't embed a write closure within another, and adding observers should occur outside of a write closure as well - the error indicates the observer is being attached within a closure
Is running a query the only way of re-initializing a Realm Object for use in a different thread? Is there no way to somehow grab some kind of reference for use with Realm, so that the query doesn't have to be completely from scratch? It would be nice to have some kind of guarantee that the object is for the same record (if it exists and the query for that "ref" is successful), without having to add a primary key just for this one purpose.
This outlines my situation:
func construct(name: String, tokens: [String]) -> Document {
let doc = Document()
let realm = try! Realm()
try! realm.write {
realm.add(doc)
}
DispatchQueue.global(qos: .userInitiated).async {
let realm = try! Realm()
// Some long running task to convert `tokens` into `[Blurb]`
// var blurbs: [Blurb]
// What's the recommended way of re-initing that new `Document`?
// let sameDoc = .....
try! realm.write {
sameDoc.blurbs.append(blurbs)
}
}
return doc
}
I want to create a duplicate of a persisted object such that the new instance has all the same values but is not attached to Realm. Using Object(value: persistedInstance) works great for classes whose properties are all strings, dates, numbers, etc. However, when duplicating an instance of a class with List type properties, the duplicate's List and the List's elements continue to reference the persisted records. How can I create a duplicate that's fully detached from Realm, including any Lists and elements in those Lists?
You can make a deep copy of your object via the following extension functions:
import UIKit
import Realm
import RealmSwift
protocol RealmListDetachable {
func detached() -> Self
}
extension List: RealmListDetachable where Element: Object {
func detached() -> List<Element> {
let detached = self.detached
let result = List<Element>()
result.append(objectsIn: detached)
return result
}
}
#objc extension Object {
public func detached() -> Self {
let detached = type(of: self).init()
for property in objectSchema.properties {
guard
property != objectSchema.primaryKeyProperty,
let value = value(forKey: property.name)
else { continue }
if let detachable = value as? Object {
detached.setValue(detachable.detached(), forKey: property.name)
} else if let list = value as? RealmListDetachable {
detached.setValue(list.detached(), forKey: property.name)
} else {
detached.setValue(value, forKey: property.name)
}
}
return detached
}
}
extension Sequence where Iterator.Element: Object {
public var detached: [Element] {
return self.map({ $0.detached() })
}
}
Use
/// in collections
let data = realm.objects(AbcDfg.self).detached
/// single object
data.first?.detached()
This is not yet supported natively by Realm, but a requested feature tracked by issue #3381.
For now, you would need to implement your own deep copy constructor. A common strategy is to do that on every model and call the deep copy constructors of related objects. You need to pay attention though that you don't run into cycles.
We use ObjectMapper to create a deep copy of the object by turning into JSON and then turn that JSON back into same object except it's not associated with Realm.
Mike.
As mentioned in the issue tracked at #3381, the solution for now is an implementation to create detached copies from Realm objects.
There is a better version of the detachable object implementation at
https://github.com/realm/realm-cocoa/issues/5433#issuecomment-415066361.
Incase the link doesnot work, the code by Alarson93 is:
protocol DetachableObject: AnyObject {
func detached() -> Self
}
extension Object: DetachableObject {
func detached() -> Self {
let detached = type(of: self).init()
for property in objectSchema.properties {
guard let value = value(forKey: property.name) else { continue }
if property.isArray == true {
//Realm List property support
let detachable = value as? DetachableObject
detached.setValue(detachable?.detached(), forKey: property.name)
} else if property.type == .object {
//Realm Object property support
let detachable = value as? DetachableObject
detached.setValue(detachable?.detached(), forKey: property.name)
} else {
detached.setValue(value, forKey: property.name)
}
}
return detached
}
}
extension List: DetachableObject {
func detached() -> List<Element> {
let result = List<Element>()
forEach {
if let detachable = $0 as? DetachableObject {
let detached = detachable.detached() as! Element
result.append(detached)
} else {
result.append($0) //Primtives are pass by value; don't need to recreate
}
}
return result
}
func toArray() -> [Element] {
return Array(self.detached())
}
}
extension Results {
func toArray() -> [Element] {
let result = List<Element>()
forEach {
result.append($0)
}
return Array(result.detached())
}
}
In Realm 5.0.0 freeze() method has been added.
according to release notes:
Add support for frozen objects. Realm, Results, List and Object now have freeze() methods which return a frozen copy of the object. These objects behave similarly to creating unmanaged deep copies of the source objects. They can be read from any thread and do not update when writes are made to the Realm, but creating frozen objects does not actually copy data out of the Realm and so can be much faster and use less memory. Frozen objects cannot be mutated or observed for changes (as they never change). (PR #6427).
Previously answered here
As of now, Dec 2020, there is not proper solution of this issue. We have many workarounds though.
Here is the one I have been using, and one with less limitations in my opinion.
Make your Realm Model Object classes conform to codable
class Dog: Object, Codable{
#objc dynamic var breed:String = "JustAnyDog"
}
Create this helper class
class RealmHelper {
//Used to expose generic
static func DetachedCopy<T:Codable>(of object:T) -> T?{
do{
let json = try JSONEncoder().encode(object)
return try JSONDecoder().decode(T.self, from: json)
}
catch let error{
print(error)
return nil
}
}
}
Call this method whenever you need detached / true deep copy of your Realm Object, like this:
//Suppose your Realm managed object: let dog:Dog = RealmDBService.shared.getFirstDog()
guard let detachedDog = RealmHelper.DetachedCopy(of: dog) else{
print("Could not detach Dog")
return
}
//Change/mutate object properties as you want
detachedDog.breed = "rottweiler"
As you can see we are piggy backing on Swift's JSONEncoder and JSONDecoder, using power of Codable, making true deep copy no matter how many nested objects are there under our realm object. Just make sure all your Realm Model Classes conform to Codable.
Though its NOT an ideal solution, but its one of the most effective workaround.
I am getting one more issue regarding write block - 'SharedRealm.realm.write()' - Error - "Call can throw, but it is not marked with 'try' and the error is not handled", how it can be handled.
This is the code:
func addItems(items:[Item]) {
do {
let rlm = try Realm()
rlm.write { () -> Void in
for item in items {
rlm.add(item, update: true)
}
}
}
catch let rlmError {
print("Realm() generated error: \(rlmError)")
}
}
Still I am getting the same error - ":13: Call can throw but is not marked with 'try'"
rlm.write { ... } might throw an error as well as the initializer so you will need to add the try operator in front of this invocation, too.
Since Realm() might throw an error you'll need to wrap it in a do { try } catch block
extension Realm {
public class func saveClosure(dbClosure: (Realm)->()) {
do {
let rlm = try Realm()
rlm.write { () -> Void in
dbClosure(rlm)
}
}
catch let rlmError{
print("Realm() generated error: \(rlmError)")
}
}
}
Checkout https://gist.github.com/edwardIshaq/b5810ab35c30df10af24