I've read through the relevant sections of Apple's Swift iBook (Type Casting & Protocols) but I can seem to find a way to specify that an object is an instance of a particular class that conforms to a specific protocol.
As an example in tableView(_: , cellForRowAt: )
I would like to cast the cell returned by tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath)
as being a subclass of UITableViewCell
that conforms to the RLMEntityCapableCell
protocol (Just specifies that conformers have a variable called item
that is an instance of Object
, or one of its subclasses).
This route works but the double casting seems excessive:
protocol RLMEntityCapableCell: class {
var item: Object { get set }
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath) as! RLMEntityCapableCell // Cast here so we can set item
cell.item = items[indexPath.row]
return cell as! UITableViewCell // Cast again so the return type is right…
}
This other approach:
var cell = tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath)
as! RLMEntityCapableCell, UITableViewCell
gives this error:
type annotation missing in pattern
so clearly isn't the right way to do it either.
I would prefer to specify that in order to conform to the protocol an object has to inherit from either UITableViewCell
or UICollectionViewCell
but the base of a protocol can only be limited to the class type and no further.
Edit:
The idea here is to have a generic data source for Realm objects that leverages generics much like Array
and Dictionary
do. The cells used in each table view would be specific to the entity to be displayed but the data source would only know that the cell would be a subclass of UITableViewCell
that conforms to RLMEntityCapableCell
. All the data source needs to worry about is telling the cell what instance (that would always be a subclass of Object
) it needs to display, the cell would take it from there and configure itself as needed.
No, this isn't possible... yet.
The next Swift release (version 4) might bring what you are looking for, a new feature called Class and Subtype Existentials:
This proposal brings more expressive power to the type system by allowing Swift to represent existentials of classes and subtypes which conform to protocols.
The proposal keeps the existing &
syntax but allows one of the elements to be either AnyObject
or of class type (e.g., SomeClass & SomeProtocol
).
You could then say:
var cell = tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath)
as! UITableViewCell & RLMEntityCapableCell
But, of course, you won't be able to use this to add a superclass requirement to your RLMEntityCapableCell
protocol (as you initially wished). We may need to wait for Swift 5 for that :)
Some other examples using the above Class and Subtype Existentials (Swift 4) feature:
protocol P {}
struct S {}
class C {}
class D : P {}
class E : C, P {}
let u: S & P // Compiler error: S is not of class type
let v: C & P = D() // Compiler error: D is not a subtype of C
let w: C & P = E() // Compiles successfully
and:
protocol P {}
class C {}
class D : C { }
class E : C { }
class F : D, P { }
let t: C & D & P = F() // Okay: F is a subclass of D and conforms to P
let u: D & P = t // Okay: D & P is equivalent to C & D & P
let v: C & D & P = u // Okay: C & D & P is equivalent to D & P
let w: D & E & P // Compiler error: D is not a subclass of E or vice-versa
UITableViewCell
doesn't have an item
property, so what you probably want to do is create a subclass of it that conforms to your protocol, and then cast cell
as that.
protocol RLMEntityCapableCell: class {
var item: Object { get set }
}
class RLMCell: UITableViewCell, RLMEntityCapableCell {
var item: Object
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath) as? RLMCell ?? RLMCell()
cell.item = items[indexPath.row]
return cell
}
Notes:
1. dequeueReusableCell
returns an optional and will almost certainly return nil in some cases, so don't force unwrap it.
2. You'll need to do at least one of the following: make item
optional, provide a default value, or add an initializer function.