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:
-
It looks redundant, not DRY, etc.
-
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."
-
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!