Mike Pollard Mike Pollard - 2 months ago 9
iOS Question

Swift Generics & Protocols

I'm trying to build a generic 'NSFetchResultsController'-like set of protocols in swift so that I might isolate my

UITableViewDataSource/UICollectionViewDataSource
implementations from any specifics about where and how the data is being sourced and updated.

So I began with a couple of simple definitions of the core controller and sections thus:

import UIKit
import CoreData

public protocol ResultsSection {
typealias T

var numberOfObjects : Int { get }
var objects : [T] { get }

subscript(index: Int) -> T? { get }
}

public protocol ResultsController {

typealias ResultsSection
typealias T

var resultsSections : [ResultsSection] { get }

subscript(indexPath: NSIndexPath) -> T? { get }
}


Following this a couple of simple implementations just holding everything in arrays thus:

class SimpleResultsSection<T> : ResultsSection {

private(set) var objects : [T]

init(objects: [T]?) {
self.objects = objects ?? [T]()
}

var numberOfObjects : Int {
get {
return objects.count
}
}

subscript(index: Int) -> T? {

return objects.count > index ? objects[index] : nil
}
}

class SimpleResultsController<T, RS: ResultsSection where T == RS.T> : ResultsController {

internal(set) var resultsSections = [RS]()

subscript(indexPath: NSIndexPath) -> T? {

if resultsSections.count > indexPath.section {
let section = resultsSections[indexPath.section]
return section[indexPath.row]
}
else {
return nil
}
}

init(singleSectionObjects: [T]) {
let section = SimpleResultsSection(objects: singleSectionObjects)
resultsSections.append(section)
}
}


Now on the last line of code where I try to append a
SimpleResultsSection<T>
to
self.resultsSections
the compiler denies me with:

Cannot invoke 'append' with an argument list of type (SimpleResultsSection<T>)


What have I missed here?

I thought that
<T, RS: ResultsSection where T == RS.T>
would mean that the compiler would be able to resolve
SimpleResultsSection<T>
to an
RS
and thus allow me to
append
a
SimpleResultsSection<T>
to an
[RS]
, but clearly I'm missing something.

Answer

You're not making as strong-enough promise here:

internal(set) var resultsSections = [RS]()

This only promises that the array is full of ResultsSection where T == RS.T, but that could be a completely unrelated class. Swift arrays are not covariant in that way. If they were, you to treat [Apple] as [Fruit] and then append(orange).

What you want here is:

internal(set) var resultsSections = [SimpleResultsSection<T>]()

That's a stronger promise that all of the elements inherit from the same class, while still respecting your earlier protocol promise that it could be read as [ResultSection].

That said, I would do it a bit differently. Unless you really need SimpleResultsController to be able to accept multiple types of sections, I would force that with a typealias rather than parameterizing it:

class SimpleResultsController<T> : ResultsController {
    internal(set) var resultsSections = [SimpleResultsSection<T>]()
    ...

This way, the type is SimpleResultsController<Int> rather than SimpleResultsController<Int, SimpleResultsSection<Int>> (which is a very cumbersome type).

The system can infer the type of ResultsSection from the definition of resultsSection, so there's no need to typealias it if you don't want to (though it can be handy to do that for clarity).