SwiftUI 列表数据的可识别协议扩展
Indentifiable Protocol extension for SwiftUI list data
我正在试验 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)
重新引入它。有没有更好的方法可以让我在协议级别保持可识别的合规性?
一个简单的 type-eraser 就可以解决这个问题。
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}
}
}
除了通过 .identified(by: \.id)
添加 Identifiable 之外,唯一的解决方案是使用类型擦除模式。这使用 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 {}
我正在试验 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)
重新引入它。有没有更好的方法可以让我在协议级别保持可识别的合规性?
一个简单的 type-eraser 就可以解决这个问题。
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}
}
}
除了通过 .identified(by: \.id)
添加 Identifiable 之外,唯一的解决方案是使用类型擦除模式。这使用 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 {}