Search code examples
swiftcocoa-touchdesign-patternsrealmcocoapods

Pattern when using Realm in a Cocoa Touch Framework


This is a code pattern/design question when using the amazing Realm data base inside a Cocoa Touch Framework. Specifically, a framework that will be distributed as a cocoapod.

Lets say I have a some realm objects inside a framework I am building

public class Dog: Object {
    @objc public private(set) dynamic var name: String?
    public let age = RealmOptional<Int>()
    public private(set) var owner: Person?
}

public class Person: Object {
    @objc public private(set) dynamic var name: String?
    public private(set) var dogs = LinkingObjects(fromType: Dog.self, property: "owner")
}

Now, I want the consumer of my framework to be able to interact with these objects. I don't want to abstract these with an MVVM pattern because I want the user of my framework to be able to take advantage of some great Realm stuff like querying, sorting, and most importantly, Realm change notifications.

So, first question. Should I let users of my framework initialize objects directly? They will have these options already with the Realm initializers and if they choose to use them they are responsible for them. But, I like to go with factory pattern using static methods. Like this:

extension Dog {
    public static func retreiveManagedDog() throws -> Dog {
        let dog = Dog()
        do{
            let realm = try Realm()
            realm.beginWrite()

            realm.add(dog)
            try realm.commitWrite()
        }catch{
            throw error
        }
        return dog
    }
}

Is this a good design pattern for this use case?

Second, the next problem is updating objects. Since all Realm objects have to be updated inside a write transaction I don't want the user of my framework to have to write a bunch of boilerplate code just to change a name. So, I write functions like this:

//MARK: Extension that has functions to update properties
extension Dog {
    public func updateName(_ name: String?) throws {
        do{
            let realm = try Realm()
            realm.beginWrite()
            self.name = name
            try realm.commitWrite()
        }catch{
            throw error
        }
    }
}

Notice my object definitions had private(set) just for this reason. It will help force the user of my framework to use my setter methods.

In general, am I insane for trying to wrap Realm this way? Other great frameworks that persist usually wrap all the SQL Lite/Core Data logic. I would also like suggestions to improve this pattern in general.


Solution

  • Fortunately, I don't think there are any hard-and-fast right answers to your questions. It really depends on what you're trying to do.

    First off, it's a good idea not to tamper with the default Realm or the default Realm configuration, since it's likely that any application that uses Realm directly may manipulate either as well. You'll also want to make sure you namespace your model types and exclude them from the default schema. (Likewise, you will probably want to open your framework's Realms using only the schema of the models you define internally.)

    As for your API, what it looks like is going to be determined by what you want to use Realm for in the context of your framework.

    If Realm is an implementation detail, it makes sense to control how users interact with your Realm-based objects through APIs like the ones you've described above. That way you can control exactly what users can do with the model objects, and ensure that they can't be modified in such a way that their state becomes invalid (if that is a concern).

    For example, you may want to handle registering and deregistering observer blocks on a Realm object for the user via an API you provide that specifies its own preconditions and requirements. This might be useful if the period of time which an object should be observed should coincide with or be controlled by some other object or system that's part of your framework: instead of telling users how to store their notification tokens and when the proper time is to dispose of them, you can take care of all those details behind the scenes and ensure the user can't misuse Realm within the context of your framework.

    You can even take this to an extreme and make the use of Realm invisible to your users by conforming your Realm types to some protocol(s) and having your APIs consume and vend that protocol type. That way your users can't tell that your objects are Realm objects and won't be tempted to stick them in their own Realms, copy them, etc.

    If, however, you intend for users to know that you're providing them with Realm objects in your API, and perhaps even to use them alongside their own use of Realm (e.g. putting them in their own Realms), then it makes sense to explicitly vend them out as Realm objects, perhaps with helper APIs like the ones you described strictly for convenience reasons. In this case you'll want to think about how your framework's use of Realm can avoid interfering with your consumer's use of Realm.