30

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?

mfaani
  • 33,269
  • 19
  • 164
  • 293
Jason Leach
  • 3,889
  • 7
  • 37
  • 54

6 Answers6

33

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.

caseynolan
  • 1,236
  • 1
  • 9
  • 11
16

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

JDune
  • 567
  • 7
  • 10
7

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?
}
da-na
  • 250
  • 2
  • 8
  • 3
    Thanks da-na, it seems very clean. However I do not understand what happens if Realm() fails, what does return self.realm in that case?? Thank you!! – Eva Madrazo Apr 21 '17 at 21:01
  • 12
    It will get into an infinite loop unfortunately, and the app would freeze. So definitely there's space for improvement here. – da-na Apr 24 '17 at 20:52
5

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
}
10623169
  • 984
  • 1
  • 12
  • 22
Pablo Sanchez Gomez
  • 1,438
  • 16
  • 28
  • @james I've been testing this solution, guard let realm = Realm.safeInit() else { return } // Then your write transaction body: realm.safeWrite { ... } – 10623169 Jun 26 '19 at 10:24
  • 1
    @zb1995 I have changed ur approach with the use of guard let :) – Pablo Sanchez Gomez Jun 26 '19 at 11:47
  • 1
    Excellent, I have just proposed another edit that makes the `safeWrite` method async-safe for "Realm already in a write transaction" exceptions (for example, if Realm becomes full): https://github.com/realm/realm-cocoa/issues/4511#issuecomment-270962198 – 10623169 Jul 02 '19 at 14:21
1

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.

BergQuester
  • 6,167
  • 27
  • 39
0

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)")
        }
    }
}
YanSte
  • 10,661
  • 3
  • 57
  • 53