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 ответа:
Нет, это невозможно... пока что .
Следующий релиз 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
необязательным, указать значение по умолчанию или добавить функцию инициализатора.