【问题标题】:Storing objects conforming to a protocol with generics in a typed array在类型化数组中存储符合泛型协议的对象
【发布时间】:2019-02-22 22:29:56
【问题描述】:

我有一个协议:

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 调整集:[可调整] = []

看了一圈,一开始以为是协议不符合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)

【问题讨论】:

    标签: ios swift generics protocols generic-collections


    【解决方案1】:

    您不能拥有Adjustable 类型的项目数组,因为Adjustable 并不是真正的类型。这是一个描述一组类型的蓝图,每个可能的 T 值都有一个。

    要解决这个问题,您需要使用类型橡皮擦https://medium.com/dunnhumby-data-science-engineering/swift-associated-type-design-patterns-6c56c5b0a73a

    【讨论】:

    • 在您的回答中包含一个示例,以防文章消失?
    • (天哪,如果我们可以强制数组元素符合协议,这会容易得多)
    • @brandonscript 我没有时间输入它。我不确定是否应该将此作为评论或答案发布。
    • @brandonscript 是的,会的。期待已久的功能称为"Generalized Existentials"
    【解决方案2】:

    在亚历山大的建议下取得了很大的进步;我能够使用一些嵌套类类型来继承基类型擦除类,并使用符合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,其中有一个关于如何实现泛型类型橡皮擦的简单明了的示例。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多