Расширение Indentifiable Protocol для данных списка SwiftUI

Я экспериментировал со SwiftUI и столкнулся с проблемой при реализации модели данных для одного из моих списков. Мой план состоял в том, чтобы создать протокол CardProtocol в качестве протокола данных для элементов моих списков, а затем иметь реализацию протокола CoreData, а также фиктивную реализацию для модульного тестирования и использования Canvas. Если вы используете сбор данных в SwiftUI List, отдельные элементы должны соответствовать Identifiable протоколу.

Код выглядит так:

import SwiftUI
import Combine


final class CardsModel: BindableObject {
    var cards: [CardProtocol] = []
    let didChange = PassthroughSubject<CardsModel, Never>()
}

protocol CardProtocol: Identifiable {
    var id: Int { get set }
    var firstName: String? { get set }
    var lastName: String? { get set }
    var email: String? { get set }
    var phone: String? { get set }
}

Это даже не будет компилироваться, поскольку протокол Identifiable имеет 2 связанных типа, которые необходимо указать, если протокол будет использоваться для определения переменной.

/// A type that can be compared for identity equality.
public protocol Identifiable {

    /// A type of unique identifier that can be compared for equality.
    associatedtype ID : Hashable

    /// A unique identifier that can be compared for equality.
    var id: Self.ID { get }

    /// The type of value identified by `id`.
    associatedtype IdentifiedValue = Self

    /// The value identified by `id`.
    ///
    /// By default this returns `self`.
    var identifiedValue: Self.IdentifiedValue { get }
}

Точная ошибка error: protocol 'CardProtocol' can only be used as a generic constraint because it has Self or associated type requirements. Теперь ID не проблема и может быть исправлена, но IdentifiedValue она по своей природе отличается в CoreData и фиктивной реализации.

Единственное разумное решение, которое я нашел, заключалось в том, чтобы удалить соответствие Identifiable из протокола и повторно ввести его позже в представлении с помощью cardsModel.cards.identified(by: \.id). Есть ли лучший выход из этого, который позволил бы мне сохранить идентифицируемое соответствие на уровне протокола?


person Daniele Bernardini    schedule 11.06.2019    source источник
comment
cardsModel.cards.identified(by: \.id) кажется более чистым решением с меньшим количеством шаблонов   -  person Matteo Pacini    schedule 12.06.2019
comment
Хотя это не оптимально, я все же хотел бы, чтобы это было протоколом. Другой неоптимальный способ - согласовать каждую реализацию Identifiable независимо.   -  person Daniele Bernardini    schedule 13.06.2019


Ответы (2)


Единственное решение, помимо добавления «Идентифицируемые через .identified(by: \.id)», - это использование шаблона стирания типа. При этом используются 3 класса, чтобы упаковать и скрыть связанный тип, а затем можно объявить массив объектов AnyCard. Реализация довольно громоздкая и, вероятно, не стоит из-за моей проблемы. Но вот оно:

final class CardsModel<IdentifiedValue:CardProtocol>: BindableObject {
    var cards: [AnyCard<IdentifiedValue>] = []
    let didChange = PassthroughSubject<CardsModel, Never>()
}

protocol CardProtocol: Identifiable{
    var id: Int32 { get set }
    var firstName: String? { get set }
    var lastName: String? { get set }
    var email: String? { get set }
    var phone: String? { get set }
}

struct TestCard: CardProtocol {
    var id: Int32
    var firstName: String?
    var lastName: String?
    var email: String?
    var phone: String?
}

extension CardsModel where IdentifiedValue == TestCard {
    convenience init(cards: [TestCard]) {
        self.init()
        self.cards = cards.map({ (card) -> AnyCard<TestCard> in
            return AnyCard(card)
        })
    }
}

private class _AnyCardBase<IdentifiedValue>: CardProtocol {
    init() {
        guard type(of: self) != _AnyCardBase.self else {
            fatalError("_AnyCardBase<Model> instances can not be created; create a subclass instance instead")
        }
    }

    var id: Int32 {
        get { fatalError("Must override") }
        set { fatalError("Must override") }
    }

    var firstName: String? {
        get { fatalError("Must override") }
        set { fatalError("Must override") }
    }

    var lastName: String? {
        get { fatalError("Must override") }
        set { fatalError("Must override") }
    }

    var email: String? {
        get { fatalError("Must override") }
        set { fatalError("Must override") }
    }

    var phone: String? {
        get { fatalError("Must override") }
        set { fatalError("Must override") }
    }
}


private final class _AnyCardBox<Concrete: CardProtocol>: _AnyCardBase<Concrete.IdentifiedValue> {
    var concrete: Concrete

    init(_ concrete: Concrete) {
        self.concrete = concrete
    }

    override var id: Int32 {
        get {
            return concrete.id
        }
        set {
            concrete.id = newValue
        }
    }

    override var firstName: String? {
        get {
            return concrete.firstName
        }
        set {
            concrete.firstName = newValue
        }
    }

    override var lastName: String? {
        get {
            return concrete.lastName
        }
        set {
            concrete.lastName = newValue
        }
    }

    override var email: String? {
        get {
            return concrete.email
        }
        set {
            concrete.email = newValue
        }
    }

    override var phone: String? {
        get {
            return concrete.phone
        }
        set {
            concrete.phone = newValue
        }
    }
}

final class AnyCard<IdentifiedValue>: CardProtocol {
    private let box: _AnyCardBase<IdentifiedValue>

    init<Concrete: CardProtocol>(_ concrete: Concrete) where Concrete.IdentifiedValue == IdentifiedValue {
        box = _AnyCardBox(concrete)
    }

    var id: Int32 {
        get {
            return box.id
        }
        set {
            box.id = newValue
        }
    }

    var firstName: String? {
        get {
            return box.firstName
        }
        set {
            box.firstName = newValue
        }
    }

    var lastName: String? {
        get {
            return box.lastName
        }
        set {
            box.lastName = newValue
        }
    }

    var email: String? {
        get {
            return box.email
        }
        set {
            box.email = newValue
        }
    }

    var phone: String? {
        get {
            return box.phone
        }
        set {
            box.phone = newValue
        }
    }
}

//NSManagedObject extention
extension Card:CardProtocol {}
person Daniele Bernardini    schedule 15.06.2019

Эту проблему решает простой ластик.


final class CardsModel: BindableObject {
    var cards: [AnyCardModel] = [AnyCardModel]()
    let didChange = PassthroughSubject<CardsModel, Never>()
}

protocol CardProtocol: Identifiable {
    var id: Int { get set }
    var firstName: String? { get set }
    var lastName: String? { get set }
    var email: String? { get set }
    var phone: String? { get set }
}

class AnyCardModel: CardProtocol {

    var _id: Int
    var _firstName: String?
    var _lastName: String?
    var _email: String?
    var _phone: String?

    init<T: CardProtocol>(card: T) {
        _id = card.id
        _firstName = card.firstName
        _lastName = card.lastName
        _email = card.email
        _phone = card.phone
    }

    var id: Int {
        get { return _id }
        set { _id = newValue}
    }
    var firstName: String? {
        get { return _firstName }
        set { _firstName = newValue}
    }
    var lastName: String? {
        get { return _lastName }
        set { _lastName = newValue}
    }
    var email: String?{
        get { return _email }
        set { _email = newValue}
    }
    var phone: String?{
        get { return _phone }
        set { _phone = newValue}
    }
}

person Sada    schedule 12.06.2019
comment
Я не знаю, как мне дальше двигаться дальше. Если бы я правильно понял, если бы я реализовал конкретный подкласс NSManagedObject, мне тогда нужно было бы глубоко скопировать каждый элемент в AnyCardModel, но тогда я не смог бы изменить базовый NSManagedObject и сохранить данные на диск. Если я чего-то не упускаю, я не считаю это жизнеспособным решением. - person Daniele Bernardini; 13.06.2019
comment
Хотя ответ неверен, он поставил меня на правильный путь. Я напишу решение для любого, у кого есть подобная проблема. Это действительно связано со стиранием шрифта. - person Daniele Bernardini; 15.06.2019