After writing that last post, I fell into the trap that I was afraid I would: I spent my free coding time this week working on a persistence scheme instead of on feature work on my app.

But I’m moderately happy with what I’ve come up with. As planned, it uses a weak cache in front of CouchBaseLite. CBL is a document data store. It stores indexed JSON, which we turn into Swift objects through Gloss-inspired encoding and decoding.

The whole thing is under 100 lines of code so far, and a lot of that is exception handling, so I think the ideas are pretty straightforward. It could probably use a large helping of syntactic sugar and some thought put into reference cycles and patterns for searching, but it’s a start…

I’ve cheekily nicknamed it dali, after Salvador Dalí, in honor of The Persistence of Memory.

Here’s what some familiar sample objects look like:

class Shape : Persistable {
    class var kind: String { return "Shape" }
    let identifier = NSUUID().UUIDString

    var area: Double {
        return 0.0
    }

    init() { }

    required init?(with json: JSON, from persistence: Persistence) throws { }

    func save(to: Persistence) throws {
        try to.save(self, json: [:])
    }
}

class Square : Shape {
    override class var kind: String { return "Square" }

    let side: Double

    override var area: Double {
        return side * side
    }

    init(side: Double) {
        self.side = side
        super.init()
    }

    required init?(with json: JSON, from persistence: Persistence) throws {
        guard let side: Double = "side" <~~ json else { return nil }
        self.side = side
        try super.init(with: json, from: persistence)
    }

    override func save(to: Persistence) throws {
        try to.save(self, json: ["side": side])
    }
}

class Circle : Shape {
    override class var kind: String { return "Circle" }

    let radius: Double

    override var area: Double {
        return M_PI * radius * radius
    }

    init(radius: Double) {
        self.radius = radius
        super.init()
    }

    required init?(with json: JSON, from persistence: Persistence) throws {
        guard let radius: Double = "radius" <~~ json else { return nil }
        self.radius = radius
        try super.init(with: json, from: persistence)

    }

    override func save(to: Persistence) throws {
        try to.save(self, json: ["radius": radius])
    }
}

Relationships are simply stored identifiers.

final class VennDiagram : Persistable {
    static let kind = "VennDiagram"
    let identifier = NSUUID().UUIDString

    let left: Circle
    let right: Circle

    init(left: Circle, right: Circle) {
        self.left = left
        self.right = right
    }

    required init?(with json: JSON, from persistence: Persistence) throws {
        guard let left: Circle = try persistence.load("left" <~~ json),
            right: Circle = try persistence.load("right" <~~ json)
            else { return nil }
        self.left = left
        self.right = right
    }

    func save(to: Persistence) throws {
        try left.save(to)
        try right.save(to)
        try to.save(self, json: ["left": left.identifier, "right": right.identifier])
    }

}

They can be lazily instantiated if desired.

final class LazySquare : Persistable {
    static let kind = "LazySquare"
    let identifier = NSUUID().UUIDString

    private weak var persistence: Persistence?
    private var squareIdentifier: String?
    lazy var square: Square? = try! self.persistence?.load(self.squareIdentifier)

    init(square: Square) {
        self.square = square
    }

    required init?(with json: JSON, from persistence: Persistence) throws {
        self.persistence = persistence
        self.squareIdentifier = "square" <~~ json
    }

    func save(to: Persistence) throws {
        if let square = square {
            try square.save(to)
            try to.save(self, json: ["square": square.identifier])
        } else {
            try to.save(self, json: [:])
        }
    }
}