Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 3: Is there a way to cast an object to a class and protocol at the same time?

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.

like image 463
theMikeSwan Avatar asked Apr 28 '17 18:04

theMikeSwan


1 Answers

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
like image 74
Paulo Mattos Avatar answered Sep 23 '22 05:09

Paulo Mattos