簡體   English   中英

在類型化數組中存儲符合 generics 協議的對象

[英]Storing objects conforming to a protocol with generics in a typed array

我有一個協議:

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
}

我正在構建一個包裝器 class ,其行為類似於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)

或者:

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

或者

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

您不能具有Adjustable類型的項目數組,因為Adjustable實際上不是一種類型。 這是一個藍圖,描述了一組類型,每個可能的T值一個。

要解決此問題,您需要使用類型橡皮擦https://medium.com/dunnhumby-data-science-engineering/swift-associated-type-design-patterns-6c56c5b0a73a

利用亞歷山大的建議取得了很大的進步; 我能夠使用一些嵌套的類類型來繼承基本類型的擦除類,並使用符合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 ,其中提供了有關如何實現通用類型擦除器的簡單示例。

使用 Swift 5.7,您將能夠通過在您的協議前加上any前綴,而不會出現任何編譯器錯誤,因此您的設置變為:

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

請注意,自編譯時 swift 無法確定存在類型Adjustable的大小,因為實現它的類型將具有可變大小,所以您的adjustmentSet數組中的所有項目都將在堆上分配。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM