Realm Swift

repository·community·Indexed 12 days ago

https://github.com/realm/realm-swift

A mobile-first, object-oriented database for iOS, macOS, tvOS, and watchOS. It features a reactive data model with live objects, seamless SwiftUI integration via property wrappers like @ObservedResults, and support for offline-first applications. The SDK allows developers to define schemas using idiomatic Swift, perform type-safe CRUD operations, and secure data with 64-byte encryption keys.

Tokens
64.1K
Snippets
179
Records
260
Agent score
96%

What's inside Realm

  1. Core Workflow: Define, Open, and Manage Data

    community

    The Realm Swift SDK follows a standard lifecycle for data management:

    1. Define an Object Schema: Use idiomatic Swift to define your data models.
    2. Open a Database: Open a Realm database stored in a file on the device, or open an in-memory database if persistence is not required.
    3. Read and Write Data: Perform CRUD (Create, Read, Update, Delete) operations. You can filter data using the type-safe .where syntax or by constructing an NSPredicate.
    4. React to Changes: Use notification handlers to watch for changes in live objects, or use SwiftUI property wrappers to automatically update your UI when data changes.
  2. Features of the Realm Plugin

    community

    The Realm Plugin enhances the Xcode development experience with the following features:

    • LLDB Scripting: Adds support for inspecting the property values of persisted RLMObject instances directly in the Xcode debugger pane.
    • File Templates: Provides pre-configured templates for creating new RLMObject subclasses.
    • Realm Browser Shortcut: Adds a menu item in Xcode's 'File' menu to quickly launch the Realm Browser.

    Note: The 'File' menu item is only available in Xcode 7 or in unsigned versions of Xcode 8 or later (the latter is not recommended).

  3. Avoid eager Realm initialization issues

    community

    If you define properties that use Realm APIs (e.g., let persons = try! Realm().objects(Person.self)) inside a class, they may be initialized before your app has finished configuring the Realm.Configuration.defaultConfiguration (for example, in applicationDidFinishLaunching). This can lead to unexpected behavior or incorrect schema usage.

    To prevent this, use one of the following patterns:

    1. Defer instantiation: Do not create instances of types that eagerly initialize Realm properties until after your Realm configuration is complete.
    2. Use lazy properties: Define Realm-dependent properties using the lazy keyword so they are only initialized when first accessed.
    3. Explicit configuration: Only initialize properties using Realm APIs that accept a specific Realm.Configuration object, ensuring you pass the correctly configured instance.
  4. Read from a Realm

    community

    Reading data from a Realm typically follows a pattern of retrieving objects of a specific type, then optionally applying filters and sorting. You can also retrieve objects divided into sections.

    Query, filter, and sort operations return either a Results or SectionedResults collection. These collections are live, meaning they automatically update to reflect the latest state of the data on disk.

  5. Follow the three rules of Realm threading

    community

    To ensure thread safety and performance when using Realm in Swift, follow these three fundamental rules:

    1. Read freely without locks: Realm uses Multiversion Concurrency Control (MVCC), meaning you can read from the same Realm file on any thread without locks or mutexes. Reads will never be corrupted or partially modified.
    2. Only one writer at a time: While you can write from any thread, only one write transaction can be active at a time. Synchronous writes block each other; a synchronous write on the UI thread can cause the app to become unresponsive if it waits for a background write to finish.
    3. Respect thread confinement: Live objects, collections, and Realm instances are thread-confined. They are only valid on the thread where they were created and cannot be passed directly to other threads. Use writeAsync, thread-safe references, or frozen objects to share data across threads.
  6. Use automatic compaction

    community

    Starting in SDK version 10.35.0, Realm provides automatic compaction. The SDK continuously reallocates data within the file and removes unused space in the background.

    Trigger Condition: Automatic compaction begins when the size of unused space in the file is more than twice the size of the actual user data.

    Constraint: Automatic compaction only occurs when the file is not being actively accessed.

  7. How live collections work and best practices

    community

    Most Realm collections are live, meaning they automatically update to reflect the current state of the database.

    When collections are NOT live:

    • When the collection is unmanaged (e.g., a List property on an object not yet added to a Realm, or an object copied from a Realm).
    • When the collection is frozen.

    Critical Best Practice: Because live collections update automatically, do not store the positional index or the count of objects in a collection. By the time you attempt to use a stored index or count, the underlying collection may have changed, making your stored value outdated and potentially leading to errors.

  8. Simplify testing with Class Projections

    community

    Introduced in version 10.21.0, Class Projections allow you to work with a subset of an object's properties. This abstraction lets you pass through, rename, or exclude properties, which simplifies both View Model implementation and testing by allowing you to ignore properties that are irrelevant to the specific test case.

    func testWithProjection() {
        let realm = try! Realm()
        // Create a Realm object, populate it with values
        let jasonBourne = Person(value: ["firstName": "Jason",
                                                           "lastName": "Bourne",
                                                           "address": [
                                                            "city": "Zurich",
                                                            "country": "Switzerland"
                                                            ]])
        try! realm.write {
            realm.add(jasonBourne)
        }
    
        // Retrieve all class projections of the given type `PersonProjection`
        // and filter for the first class projection where the `firstName` property
        // value is "Jason"
        let person = realm.objects(PersonProjection.self).first(where: { $0.firstName == "Jason" })!
        // Verify that we have the correct PersonProjection
        XCTAssert(person.firstName == "Jason")
        // See that `homeCity` exists as a projection property
        // Although it is not on the object model
        XCTAssert(person.homeCity == "Zurich")
    
        // Change a value on the class projection
        try! realm.write {
            person.firstName = "David"
        }
    
        // Verify that the projected property's value has changed
        XCTAssert(person.firstName == "David")
    }
  9. Handle Object Change Notifications

    community

    When observing a single Realm object, the notification block provides a copy of the object that is isolated to the requested actor, along with details about what changed.

    Change Types

    • .change(object, properties): The object was modified. The properties array contains the names and new values of the changed properties.
    • .deleted: The object was deleted from the Realm.
    • .error(Error): An error occurred.

    Property Filtering

    By default, notifications are triggered by direct changes to the object's properties. If you want to observe changes to linked objects, you must pass a non-nil, non-empty keypath array to the .observe method. These keypaths can traverse link properties.

    // Registering an object observer on a background actor
    let token = await myObject.observe(on: backgroundActor) { actor, change in
        switch change {
        case .change(let object, let properties):
            for property in properties {
                print("Property \(property.name) changed to \(property.newValue!)")
            }
        case .deleted:
            print("Object deleted")
        case .error(let error):
            print("Error: \(error)")
        }
    }
    
    // Invalidate when done
    token.invalidate()
  10. Use the Realm Swift Query API

    community

    The Realm Swift Query API provides an idiomatic, type-safe way to query data using Swift-style syntax. It offers the benefits of auto-completion and type safety.

    Note that the Realm Swift Query API does not replace the NSPredicate Query API; you can use either depending on your needs. For SDK versions prior to 10.19.0, or for Objective-C development, you must use NSPredicate queries.

    // Example of accessing objects to query them
    let tasks = realm.objects(Task.self)
    let projects = realm.objects(Project.self)
  11. Use interface-driven writes (silent writes) to prevent UI double-updates

    community

    Interface-driven writes (also known as silent writes) allow you to perform a write transaction without triggering standard Realm notifications.

    When to use them:

    Use silent writes when you are manually managing a UI component (like a UITableView data source) and need to update the UI instantly. If you update the UI manually and then wait for Realm's asynchronous notification to arrive, the notification might trigger a second, redundant update. This can lead to inconsistent state or app crashes.

    Pattern:

    • User-driven updates: Use an interface-driven write to update the data and immediately trigger UI animations/updates.
    • Background/Sync updates: Use standard writes so that Realm's notification system can automatically update the UI for changes coming from other sources (like a sync process).