Swift 3: Есть ли способ привести объект к классу и протоколу одновременно?


Я прочитал соответствующие разделы Swift iBook от Apple (Type Casting & Protocols), но мне кажется, что я могу найти способ указать, что объект является экземпляром определенного класса, который соответствует определенному протоколу.

В качестве примера в tableView(_: , cellForRowAt: ) я хотел бы привести ячейку, возвращаемую tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath) , как подкласс UITableViewCell, который соответствует протоколу RLMEntityCapableCell (просто указывает, что у конформеров есть переменная под названием item, которая является экземпляром Object, или одним из его подклассы).

Этот маршрут работает, но двойное литье кажется чрезмерным:

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…
}

Этот другой подход:

var cell = tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath) 
           as! RLMEntityCapableCell, UITableViewCell

Выдает такую ошибку:

Тип аннотации отсутствует в шаблоне

Так что очевидно, что это тоже не правильный способ сделать это.

Я бы предпочел указать, что для соответствия протоколу объект должен наследовать либо от UITableViewCell, либо от UICollectionViewCell, но база протокола может быть ограничена только типом класса и больше ничего.

Правка:

Идея здесь состоит в том, чтобы иметь общий источник данных для объектов области, который использует универсальные методы, такие как Array и Dictionary. Ячейки, используемые в каждом табличном представлении, будут специфичны для отображаемой сущности, но источник данных будет знать только то, что ячейка будет подклассом UITableViewCell, который соответствует RLMEntityCapableCell. Все, о чем должен беспокоиться источник данных, - это сообщить ячейке, какой экземпляр (который всегда будет подклассом Object) ей нужно отобразить, ячейка возьмет его оттуда и настроит себя по мере необходимости.
2 8

2 ответа:

Нет, это невозможно... пока что .

Следующий релиз Swift (версия 4) может принести то, что вы ищете, новую функцию под названием экзистенциалы классов и подтипов:

Это предложение придает более выразительную силу системе типов, позволяя Swift представлятьэкзистенциалы классов и подтипов, которыесоответствуют протоколам.

Предложение сохраняет существующий синтаксис &, но позволяет одному из элементы должны быть либо AnyObject, либо типа класса (например, SomeClass & SomeProtocol).

Тогда вы могли бы сказать:

var cell = tableView.dequeueReusableCell(withIdentifier: reuseID, for: indexPath) 
           as! UITableViewCell & RLMEntityCapableCell
Но, конечно, вы не сможете использовать это, чтобы добавить требование суперкласса к вашему протоколу RLMEntityCapableCell (Как вы изначально хотели). Для этого нам, возможно, придется подождать Swift 5:)

Некоторые другие примеры, использующие вышеупомянутые экзистенциалы классов и подтипов (Swift 4):

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

И:

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 не имеет свойства item, поэтому вы, вероятно, захотите создать его подкласс, соответствующий вашему протоколу, а затем привести cell в качестве этого.

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
}

Примечания:
1. dequeueReusableCell возвращает необязательный и почти наверняка вернет nil в некоторых случаях, поэтому не заставляйте его разворачивать.
2. Вам потребуется выполнить хотя бы одно из следующих действий: сделать item необязательным, указать значение по умолчанию или добавить функцию инициализатора.