Skip to content

Instantly share code, notes, and snippets.

@groue
Last active August 29, 2015 14:25
Show Gist options
  • Save groue/354cf7e75e3fb0de8b36 to your computer and use it in GitHub Desktop.
Save groue/354cf7e75e3fb0de8b36 to your computer and use it in GitHub Desktop.

This blog post shows how Swift protocols can help using CoreData: http://martiancraft.com/blog/2015/07/objective-c-swift-core-data/

Following this advice, I have tried this technique, with success, but with a request for improvement.

Below you will find a bare minimal view of the API I'm working on. Notice the not nice Person.self in the line db.fetchAll(Person.self, "SELECT * FROM persons"). I want to improve that, and the linked blog post was a great deal of inspiration.

// Library
class RowModel {} // Equivalent of NSManagedObject in Core Data
class Database {
    func fetchAll<T: RowModel>(type: T.Type, _ sql: String) -> [T] {
        return []   // bare minimal, for demonstration purpose
    }
}

// Userland
class Person: RowModel {}
let db = Database()
let persons = db.fetchAll(Person.self, "SELECT * FROM persons") // It works.

Now let's apply protocols as in the linked blog post. And it works! Isn't Person.fetchAll(db, "SELECT * FROM persons") prettier?

// Library
protocol Fetchable {
    typealias FetchableType : RowModel
    static func fetchAll(db: Database, _ sql: String) -> [FetchableType]
}

extension Fetchable where Self : RowModel, FetchableType == Self {
    static func fetchAll(db: Database, _ sql: String) -> [FetchableType] {
        return db.fetchAll(FetchableType.self, sql)
    }
}

// Userland
class Person: RowModel {}
extension Person : Fetchable { typealias FetchableType = Person }
let db = Database()
let persons = Person.fetchAll(db, "SELECT * FROM persons")

Yet it could still be improved. In userland, we notice the line extension Person : Fetchable { typealias FetchableType = Person }. This line has problems:

  1. It looks redundant, not DRY, etc.

  2. It is a weakness of an API if it requires the user to add such lines that have no visible/clear purpose except that the documentation says "don't forget to add this line or it won't work."

  3. The linked blog post provides an extra set of APIs on top of the regular Core Data, which means that users have two ways to use Core Data, the regular one, and the improved one. I don't want my library to provide two sets of APIs, so I have to choose one, and the good one. I tend to prefer APIs that keeps userland code terse, and this extra line does not make it.

Could we provide the default value Self to Fetchable.FetchableType, and declaring Fetchable adoption at the base class level?

Well, as you see below, the userland code gets as minimal as it can be, and that's good. Unfortunately, this time the compiler complains. But in an unexpected way, which makes me think it can be improved:

// Library
protocol Fetchable {
    typealias FetchableType = Self
    static func fetchAll(db: Database, _ sql: String) -> [FetchableType]
}

// Declare Fetchable on the base class only.
extension RowModel : Fetchable { }

// Userland
class Person: RowModel {}
let db = Database()
let persons = Person.fetchAll(db, "SELECT * FROM persons")
// In playground: error: cannot invoke 'fetchAll' with an argument list of type '(Database, String)'
// In test case: error: 'String' is not convertible to 'StringLiteralConvertible'

Ha, those errors!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment