Хранение объектов, соответствующих протоколу с универсальными шаблонами, в типизированном массиве

У меня есть протокол:

protocol Adjustable: Equatable {
    associatedtype T
    var id: String { get set }
    var value: T { get set }
    init(id: String, value: T)
}

И структура, которая ему соответствует:

struct Adjustment: Adjustable {
    static func == (lhs: Adjustment, rhs: Adjustment) -> Bool {
        return lhs.id == rhs.id
    }

    typealias T = CGFloat
    var id: String
    var value: T
}

И я создаю класс-оболочку, который ведет себя как Set для обработки упорядоченного списка этих свойств:

struct AdjustmentSet {
    var adjustmentSet: [Adjustable] = []
    func contains<T: Adjustable>(_ item: T) -> Bool {
        return adjustmentSet.filter({ $0.id == item.id }).first != nil
    }
}

let brightness = Adjustment(id: "Brightness", value: 0)

let set = AdjustmentSet()
print(set.contains(brightness))

Но это, конечно, не работает, ошибка с:

ошибка: протокол "Adjustable" может использоваться только как общее ограничение, потому что он имеет требования типа Self или связанный с ним var adjustSet: [Adjustable] = []

Оглядываясь вокруг, я сначала подумал, что это потому, что протокол не соответствует Equatable, но потом я добавил его, но он по-прежнему не работает (или я сделал это неправильно).

Более того, я хотел бы иметь возможность использовать здесь общий тип, чтобы я мог делать что-то вроде:

struct Adjustment<T>: Adjustable {
    static func == (lhs: Adjustment, rhs: Adjustment) -> Bool {
        return lhs.id == rhs.id
    }

    var id: String
    var value: T
}

let brightness = Adjustment<CGFloat>(id: "Brightness", value: 0)

Or:

struct FloatAdjustment: Adjustable {
    static func == (lhs: Adjustment, rhs: Adjustment) -> Bool {
        return lhs.id == rhs.id
    }
    typealias T = CGFloat
    var id: String
    var value: T
}

let brightness = FloatAdjustment(id: "Brightness", value: 0)

И все же у меня будет возможность хранить массив из [Adjustable] типов, чтобы в конечном итоге я мог:

var set = AdjustmentSet()
if set.contains(.brightness) {
    // Do something!
}

Or

var brightness = ...
brightness.value = 1.5
set.append(.brightness)

person brandonscript    schedule 22.02.2019    source источник


Ответы (2)


У вас не может быть массива элементов типа Adjustable, потому что Adjustable на самом деле не является типом. Это чертеж, описывающий набор типов, по одному на каждое возможное значение T.

Чтобы обойти это, вам нужно использовать ластик типа https://medium.com/dunnhumby-data-science-engineering/swift-associated-type-design-patterns-6c56c5b0a73a

person Alexander    schedule 22.02.2019
comment
Не хотите ли включить в свой ответ пример на случай, если статья исчезнет? - person brandonscript; 23.02.2019
comment
(И, черт возьми, это было бы намного проще, если бы мы могли просто заставить элементы массива соответствовать протоколу) - person brandonscript; 23.02.2019
comment
@brandonscript У меня нет времени его печатать. Я не был уверен, стоит ли публиковать это как комментарий или как ответ. - person Alexander; 23.02.2019
comment
@brandonscript И да, было бы. Есть долгожданная функция под названием Generalized Existentials. - person Alexander; 23.02.2019

Добились большого прогресса, используя предложение Александра; Я смог использовать некоторые типы вложенных классов для наследования класса стирания базового типа и использовать общий протокол, соответствующий AnyHashable, поэтому я могу использовать его с набором!

// Generic conforming protocol to AnyHashable
protocol AnyAdjustmentProtocol {
    func make() -> AnyHashable
}

protocol AdjustmentProtocol: AnyAdjustmentProtocol {
    associatedtype A
    func make() -> A
}

struct AdjustmentTypes {
    internal class BaseType<T>: Hashable {

        static func == (lhs: AdjustmentTypes.BaseType<T>, rhs: AdjustmentTypes.BaseType<T>) -> Bool {
            return lhs.name == rhs.name
        }

        typealias A = T

        var hashValue: Int { return name.hashValue }

        let name: String
        let defaultValue: T
        let min: T
        let max: T
        var value: T

        init(name: String, defaultValue: T, min: T, max: T) {
            self.name = name
            self.defaultValue = defaultValue
            self.min = min
            self.max = max
            self.value = defaultValue
        }
    }

    class FloatType: BaseType<CGFloat> { }

    class IntType: BaseType<Int> { }
}

struct AnyAdjustmentType<A>: AdjustmentProtocol, Hashable {
    static func == (lhs: AnyAdjustmentType<A>, rhs: AnyAdjustmentType<A>) -> Bool {
        return lhs.hashValue == rhs.hashValue
    }

    private let _make: () -> AnyHashable
    private let hashClosure:() -> Int

    var hashValue: Int {
        return hashClosure()
    }

    init<T: AdjustmentProtocol & Hashable>(_ adjustment: T) where T.A == A {
        _make = adjustment.make
        hashClosure = { return adjustment.hashValue }
    }
    func make() -> AnyHashable {
        return _make()
    }
}

struct Brightness: AdjustmentProtocol, Hashable {
    func make() -> AnyHashable {
        return AdjustmentTypes.FloatType(name: "Brightness", defaultValue: 0, min: 0, max: 1)
    }
}
struct WhiteBalance: AdjustmentProtocol, Hashable {
    func make() -> AnyHashable {
        return AdjustmentTypes.IntType(name: "White Balance", defaultValue: 4000, min: 3000, max: 7000)
    }
}

let brightness = Brightness().make()
let whiteBalance = WhiteBalance().make()

var orderedSet = Set<AnyHashable>()

orderedSet.insert(brightness)
print(type(of: orderedSet))
print(orderedSet.contains(brightness))

for obj in orderedSet {
    if let o = obj as? AdjustmentTypes.FloatType {
        print(o.value)
    }
    if let o = obj as? AdjustmentTypes.IntType {
        print(o.value)
    }
}

Распечатки:

Set<AnyHashable>
true
0.0

Особая благодарность за эту статью: https://medium.com/@chris_dus/type-erasure-in-swift-84480c807534, в котором был простой и понятный пример того, как реализовать ластик универсального типа.

person brandonscript    schedule 23.02.2019