Swift:将泛型类型转换为具有关联类型子类的相同泛型类型

9

考虑以下类:

struct OrderedSet<T: Hashable> {}

class Exercise: Hashable {}

class StrengthExercise: Exercise {}

class CardioExercise: Exercise {}

我想要做如下事情:

var displayedExercises = OrderedSet<Exercise>() {
    didSet {
        self.tableView.reloadData()
    }
}
var cardioExercises = OrderedSet<CardioExercise>()
var strengthExercises = OrderedSet<StrengthExercise>()


@IBAction func segmentControlChanged(segmentControl: UISegmentedControl) {
    switch segmentControl.selectedSegmentIndex {
    case 0:     self.displayedExercises = self.strengthExercises
    case 1:     self.displayedExercises = self.cardioExercises
    default:    break
    }
}

但是我遇到了这个错误:
Cannot assign value of type 'OrderedSet<StrengthExercise>' to type 'OrderedSet<Exercise>

我不是很明白这一点,因为StrengthExerciseExercise的子类,并且将拥有OrderedSet<Exercise>所需的一切。 问题:
  • 为什么需要此错误?
  • 如何编写符合我要求的功能?
提交的雷达 rdar:// 23608799 关于协变和逆变的博客文章 https://www.mikeash.com/pyblog/friday-qa-2015-11-20-covariance-and-contravariance.html

2
这是一个需要协议(可执行?哈哈)比类层次结构更好的情况吗?或者也许是分类/扩展? - Cocoadelica
1
开个玩笑:对于显示的练习的didSet:函数,您会将重新加载tableview的调用包装在dispatch_async到主队列中,还是依赖于记录接口以说明必须在主队列上设置值? - Cocoadelica
@Cocoadelica 当然可以!这个想法太棒了!❤️!将dispatch_async()添加到主队列也是一个好主意。我更喜欢这种方式,因为只需要写一次,新的开发人员接手时就不必学习额外的东西。也许你应该把这个作为答案呈现出来?如果没有人提供一个解释错误原因的答案,我可以接受你的答案 :) - kylejs
@Cocoadelica 我强烈怀疑 displayedExercises 被表视图数据源回调所使用,因此如果有任何东西从除主线程之外的任何地方设置该变量,则 reloadData() 调用在错误的线程上将是你遇到麻烦的最少问题 —— 你将同时修改一个集合!因此,要么你认为 displayedExercises 只会在主线程上设置,那么 dispatch_async 是不必要的;或者如果它被多个线程调用,则需要在整个应用程序中进行额外的同步。 - Nicholas H.
@NicholasH。好的决定。在那种情况下,你是完全正确的。 - Cocoadelica
3个回答

7
抱歉,目前在Swift 2.1中不支持此功能。只支持以下转换:
- 内置集合类型对其元素类型是协变的。 - 支持函数类型之间的转换,展现了函数结果类型的协变和函数参数类型的逆变。(参见Xcode 7.1 Release Notes
由于Objective-C的泛型支持类型变化,并且在Swift 2.1中在函数类型转换方面取得了进展,因此我相信未来Swift将添加类型变异支持。在此期间,请记得提交radar,例如jlieske has
同时,您将需要复制集合或使用内置集合类型。 自从Swift成为开源项目以来的更新: 我相信Swift 3.0 Dev Roadmap中的完整泛型部分表明类型变化将在3.0中得到解决。虽然没有专门提到类型变化,但标准库中的特殊情况异常(包括类型变化)已被提及。

我想知道是否遵循CollectionType会给予这个功能?值得尝试吗? - kylejs
@kylejm 看起来你对于 CollectionType 启用了类型系统的特殊行为是正确的。 - Nicholas H.
@kylejm 根据对 @user3441734 的回答的讨论,将回答回滚到原始版本,表明仅继承自“CollectionType”是不足够的。 - Nicholas H.
谢谢Nicholas!我马上会提交一个雷达 :) - kylejs

2
由于OrderedSet<StrengthExercise>是特定类型,因此无法分配给更一般的OrderedSet<Exercise>。如果您尝试在分配后将心肺运动添加到该OrderedSet中,会发生什么,请考虑一下。
答案可能是修改将力量练习集合的内容附加到练习集合而不是分配整个类型化集合。

我仍然不明白为什么将更具体的类型分配给更一般的类型在这里是一个问题。OrderedSet是一个结构体,因此它是按值传递的?因此,如果您事先将strengthExercises分配给它,则向displayedExercises添加内容不会影响strengthExercises。而且,您无法将CardioExercise附加到displayedExercises,因为它的类型是OrderedSet<Exercise>。 - kylejs
我现在明白了!写给你的那条评论让我意识到问题所在!如果我将一个Exercise追加到displayedExercises中,而它实际上包含StrengthExercises,那该怎么办呢!这就是为什么你不能将一个OrderedSet<StrengthExercise>赋值给displayedExercises的原因!懂了!谢谢! - kylejs
1
OrderedSet<StrengthExercise> 是一种类型,它不仅仅是一个包含力量训练的有序集合。你可以根据其内容初始化一个新的 OrderedSet,并将其类型定义为 OrderedSet<Exercise>,但这绝对是一个新值和新类型。 - Joseph Lord
协变性怎么样?Swift是否不支持非内置类型的协变性?(抱歉,我不是每天都使用Swift。)如果不支持,那将会非常令人失望。即使Objective-C的轻量级泛型也支持类型变化,比如@interface NSArray<__covariant ObjectType> - Nicholas H.
我确切地知道我的代码在做什么。信不信由你 :-) 在你的代码中,变量displayedExercises = OrderedSet<Exercise>(),cardioExercises = OrderedSet<CardioExercise>(),strengthExercises = OrderedSet<StrengthExercise>(),而在我的例子中,Base = Exercise ... 结果错误信息已经足够清晰了。你必须理解,我的类MyClass也符合Hashable,但这只意味着Equatable与hashValue。MyClass的其余部分与你的Exercise无关。你们两个类都有一个超类Exercise。所以使用T:Exercise作为你的泛型类型...就这些。 - user3441734
显示剩余5条评论

1
这应该可以工作。
class Base {}
class A: Base {}
class B: Base {}

var arrBase: Array<Base> = []

var arrA: Array<A> = []
arrA.append(A())

var arrB: Array<B> = []
arrB.append(B())

arrBase = arrA   // no error
arrBase = arrB   // no error

你的问题似乎在代码的其他地方。你可以展示一下你的通用结构体OrderedSet的实现吗?看起来你正在尝试做类似于

class Base {}
class A: Base {}

let base = Base()
let a = A()

struct S<T:Base> {
    var t: T
}
var s = S(t: base)
let sa = S(t: a)
//s = sa  // error: cannot assign value of type 'S<A>' to type 'S<Base>'
let sb = S(t: a as Base)
s = sb

...这个有效

protocol P {
    func whoAmI()->Void
}
class Base:P {
    func whoAmI() {
        print("I am Base")
    }
}
class A: Base {
    override func whoAmI() {
        print("I am A")
    }
}

let base = Base()
let a = A()

struct S<T: Base> {
    var t: Base
}
var s = S(t: base)
let sa = S(t: a)
s = sa

s.t.whoAmI() // I am A

.... 伙计们,是内置类型还是不是

import Foundation
// Int and Double conforms to Hashable protocol
var a: Set<Int> = []
var b: Set<Double> = []
a = b   // IMPOSSIBLE eventhough Set<T:Hashable> is build-in Swift type

如何处理OrderedSet。
import Foundation

class Exercise: Hashable {
    var name: String = ""
    var hashValue: Int {
        return name.hashValue
    }
}
func ==(lhs: Exercise, rhs: Exercise) -> Bool {
    return lhs.name == rhs.name
}
class StrengthExercise: Exercise {}
class CardioExercise: Exercise {}
var displayedExercises = Set<Exercise>()
let strengthExercises = Set<StrengthExercise>()
let cardioExercises = Set<CardioExercise>()
displayedExercises = strengthExercises

// OK, the question is how to implement OrderedSet<T:Hashable>
// ------------------------------------------------------------------------------------------
//
//  OrderedSet.swift
//  Weebly
//
//  Created by James Richard on 10/22/14.
//  Copyright (c) 2014 Weebly. All rights reserved.
//
//  Slightly modified by user3441734 on 11/18/15
//  
//  original code OrderedSet is available under the MIT license


/// An ordered, unique collection of objects.
public struct OrderedSet<T: Hashable> {
    private var contents = [T: Index]() // Needs to have a value of Index instead of Void for fast removals
    private var sequencedContents = Array<UnsafeMutablePointer<T>>()

    /**
     Inititalizes an empty ordered set.

     :return: An empty ordered set.
     */
    public init() { }

    /**
     Initializes a new ordered set with the order and contents
     of sequence.

     If an object appears more than once in the sequence it will only appear
     once in the ordered set, at the position of its first occurance.

     :param: sequence The sequence to initialize the ordered set with.
     :return: An initialized ordered set with the contents of sequence.
     */
    public init<S: SequenceType where S.Generator.Element == T>(sequence: S) {
        // FIXME: For some reason, Swift gives the error "Cannot convert the expression's type 'S' to type 'S'" with a regular for-in, so this is a hack to fix that.
        var gen = sequence.generate()
        while let object: T = gen.next() {
            if contents[object] == nil {
                contents[object] = contents.count

                let pointer = UnsafeMutablePointer<T>.alloc(1)
                pointer.initialize(object)
                sequencedContents.append(pointer)
            }
        }
    }

    /**
     Replace, remove, or retrieve an object in the ordered set.

     When setting an index to nil the object will be removed. If
     it is not the last object in the set, all subsequent objects
     will be shifted down one position.

     When setting an index to another object, the existing object
     at that index will be removed. If you attempt to set an index
     that does not currently have an object, this is a no-op.

     :param:     index   The index to retrieve or set.
     :return:   On get operations, the object at the specified index, or nil
     if no object exists at that index.
     */
    public subscript(index: Index) -> T {
        get {
            return sequencedContents[index].memory
        }

        set {
            contents[sequencedContents[index].memory] = nil
            contents[newValue] = index
            sequencedContents[index].memory = newValue
        }
    }


    /**
     Locate the index of an object in the ordered set.

     It is preferable to use this method over the global find() for performance reasons.

     :param:     object      The object to find the index for.
     :return:    The index of the object, or nil if the object is not in the ordered set.
     */
    public func indexOfObject(object: T) -> Index? {
        if let index = contents[object] {
            return index
        }

        return nil
    }

    /// The number of objects contained in the ordered set.
    public var count: Int {
        return contents.count
    }

    /// Whether the ordered set has any objects or not.
    public var isEmpty: Bool {
        return count == 0
    }

    /**
     Tests if the ordered set contains an object or not.

     :param:     object  The object to search for.
     :return:    true if the object exists in the ordered set, otherwise false.
     */
    public func contains(object: T) -> Bool {
        return contents[object] != nil
    }

    /**
     Appends an object to the end of the ordered set.

     :param:     object  The object to be appended.
     */
    mutating public func append(object: T) {
        if contents[object] != nil {
            return
        }

        contents[object] = contents.count

        let pointer = UnsafeMutablePointer<T>.alloc(1)
        pointer.initialize(object)
        sequencedContents.append(pointer)
    }

    /**
     Appends a sequence of objects to the end of the ordered set.

     :param:     objects  The objects to be appended.
     */
    mutating public func appendObjects<S: SequenceType where S.Generator.Element == T>(objects: S) {
        var gen = objects.generate()
        while let object: T = gen.next() {
            append(object)
        }
    }

    /**
     Removes an object from the ordered set.

     If the object exists in the ordered set, it will be removed.
     If it is not the last object in the ordered set, subsequent
     objects will be shifted down one position.

     :param:     object  The object to be removed.
     */
    mutating public func remove(object: T) {
        if let index = contents[object] {
            contents[object] = nil
            sequencedContents[index].dealloc(1)
            sequencedContents.removeAtIndex(index)

            for (object, i) in contents {
                if i < index {
                    continue
                }

                contents[object] = i - 1
            }
        }
    }

    /**
     Removes the given objects from the ordered set.

     :param:     objects     The objects to be removed.
     */
    mutating public func removeObjects<S: SequenceType where S.Generator.Element == T>(objects: S) {
        var gen = objects.generate()
        while let object: T = gen.next() {
            remove(object)
        }
    }

    /**
     Removes an object at a given index.

     This method will cause a fatal error if you attempt to move an object to an index that is out of bounds.

     :param:     index       The index of the object to be removed.
     */
    mutating public func removeObjectAtIndex(index: Index) {
        if index < 0 || index >= count {
            fatalError("Attempting to remove an object at an index that does not exist")
        }

        remove(sequencedContents[index].memory)
    }

    /**
     Removes all objects in the ordered set.
     */
    mutating public func removeAllObjects() {
        contents.removeAll()
        sequencedContents.removeAll()
    }

    /**
     Return an OrderedSet containing the results of calling
     `transform(x)` on each element `x` of `self`

     :param:     transform   A closure that is called for each element in the ordered set.
     The result of the closure is appended to the new ordered set.
     :result:     An ordered set containing the result of `transform(x)` on each element.
     */
    public func map<U: Hashable>(transform: (T) -> U) -> OrderedSet<U> {
        var result = OrderedSet<U>()

        for object in self {
            result.append(transform(object))
        }

        return result
    }

    /// The first object in the ordered set, or nil if it is empty.
    public var first: T? {
        return count > 0 ? self[0] : nil
    }

    /// The last object in the ordered set, or nil if it is empty.
    public var last: T? {
        return count > 0 ? self[count - 1] : nil
    }

    /**
     Swaps two objects contained within the ordered set.

     Both objects must exist within the set, or the swap will not occur.

     :param:     first   The first object to be swapped.
     :param:     second  The second object to be swapped.
     */
    mutating public func swapObject(first: T, withObject second: T) {
        if let firstPosition = contents[first] {
            if let secondPosition = contents[second] {
                contents[first] = secondPosition
                contents[second] = firstPosition

                sequencedContents[firstPosition].memory = second
                sequencedContents[secondPosition].memory = first
            }
        }
    }

    /**
     Tests if the ordered set contains any objects within a sequence.

     :param:     sequence    The sequence to look for the intersection in.
     :return:    Returns true if the sequence and set contain any equal objects, otherwise false.
     */
    public func intersectsSequence<S: SequenceType where S.Generator.Element == T>(sequence: S) -> Bool {
        var gen = sequence.generate()
        while let object: T = gen.next() {
            if contains(object) {
                return true
            }
        }

        return false
    }

    /**
     Tests if a the ordered set is a subset of another sequence.

     :param:     sequence    The sequence to check.
     :return:    true if the sequence contains all objects contained in the receiver, otherwise false.
     */
    public func isSubsetOfSequence<S: SequenceType where S.Generator.Element == T>(sequence: S) -> Bool {
        for (object, _) in contents {
            if !sequence.contains(object) {
                return false
            }
        }

        return true
    }

    /**
     Moves an object to a different index, shifting all objects in between the movement.

     This method is a no-op if the object doesn't exist in the set or the index is the
     same that the object is currently at.

     This method will cause a fatal error if you attempt to move an object to an index that is out of bounds.

     :param:     object  The object to be moved
     :param:     index   The index that the object should be moved to.
     */
    mutating public func moveObject(object: T, toIndex index: Index) {
        if index < 0 || index >= count {
            fatalError("Attempting to move an object at an index that does not exist")
        }

        if let position = contents[object] {
            // Return if the client attempted to move to the current index
            if position == index {
                return
            }

            let adjustment = position < index ? -1 : 1
            let range = index < position ? index..<position : position..<index
            for (object, i) in contents {
                // Skip items not within the range of movement
                if i < range.startIndex || i > range.endIndex || i == position {
                    continue
                }

                let originalIndex = contents[object]!
                let newIndex = i + adjustment

                let firstObject = sequencedContents[originalIndex].memory
                let secondObject = sequencedContents[newIndex].memory

                sequencedContents[originalIndex].memory = secondObject
                sequencedContents[newIndex].memory = firstObject

                contents[object] = newIndex
            }

            contents[object] = index
        }
    }

    /**
     Moves an object from one index to a different index, shifting all objects in between the movement.

     This method is a no-op if the index is the same that the object is currently at.

     This method will cause a fatal error if you attempt to move an object fro man index that is out of bounds
     or to an index that is out of bounds.

     :param:     index   The index of the object to be moved.
     :param:     toIndex   The index that the object should be moved to.
     */
    mutating public func moveObjectAtIndex(index: Index, toIndex: Index) {
        if ((index < 0 || index >= count) || (toIndex < 0 || toIndex >= count)) {
            fatalError("Attempting to move an object at or to an index that does not exist")
        }

        moveObject(self[index], toIndex: toIndex)
    }

    /**
     Inserts an object at a given index, shifting all objects above it up one.

     This method will cause a fatal error if you attempt to insert the object out of bounds.

     If the object already exists in the OrderedSet, this operation is a no-op.

     :param:     object      The object to be inserted.
     :param:     atIndex     The index to be inserted at.
     */
    mutating public func insertObject(object: T, atIndex index: Index) {
        if index > count || index < 0 {
            fatalError("Attempting to insert an object at an index that does not exist")
        }

        if contents[object] != nil {
            return
        }

        // Append our object, then swap them until its at the end.
        append(object)

        for i in Range(start: index, end: count-1) {
            swapObject(self[i], withObject: self[i+1])
        }
    }

    /**
     Inserts objects at a given index, shifting all objects above it up one.

     This method will cause a fatal error if you attempt to insert the objects out of bounds.

     If an object in objects already exists in the OrderedSet it will not be added. Objects that occur twice
     in the sequence will only be added once.

     :param:     objects      The objects to be inserted.
     :param:     atIndex      The index to be inserted at.
     */
    mutating public func insertObjects<S: SequenceType where S.Generator.Element == T>(objects: S, atIndex index: Index) {
        if index > count || index < 0 {
            fatalError("Attempting to insert an object at an index that does not exist")
        }

        var addedObjectCount = 0
        // FIXME: For some reason, Swift gives the error "Cannot convert the expression's type 'S' to type 'S'" with a regular for-in, so this is a hack to fix that.
        var gen = objects.generate()

        // This loop will make use of our sequncedContents array to update the contents dictionary's
        // values. During this loop there will be duplicate values in the dictionary.
        while let object: T = gen.next() {
            if contents[object] == nil {
                let seqIdx = index + addedObjectCount
                let element = UnsafeMutablePointer<T>.alloc(1)
                element.initialize(object)
                sequencedContents.insert(element, atIndex: seqIdx)
                contents[object] = seqIdx
                addedObjectCount++
            }
        }

        // Now we'll remove duplicates and update the shifted objects position in the contents
        // dictionary.
        for i in index + addedObjectCount..<count {
            contents[sequencedContents[i].memory] = i
        }
    }
}

extension OrderedSet: MutableCollectionType {
    public typealias Index = Int
    public typealias _Element = T
    public typealias Generator = OrderedSetGenerator<T>

    public func generate() -> Generator {
        return OrderedSetGenerator(set: self)
    }

    public var startIndex: Int {
        return 0
    }

    public var endIndex: Int {
        return count
    }
}

public struct OrderedSetGenerator<T: Hashable>: GeneratorType {
    public typealias Element = T
    private var generator: IndexingGenerator<Array<UnsafeMutablePointer<T>>>

    public init(set: OrderedSet<T>) {
        generator = set.sequencedContents.generate()
    }

    mutating public func next() -> Element? {
        return generator.next()?.memory
    }
}



public func +<T: Hashable, S: SequenceType where S.Generator.Element == T> (lhs: OrderedSet<T>, rhs: S) -> OrderedSet<T> {
    var joinedSet = lhs
    joinedSet.appendObjects(rhs)

    return joinedSet
}

public func +=<T: Hashable, S: SequenceType where S.Generator.Element == T> (inout lhs: OrderedSet<T>, rhs: S) {
    lhs.appendObjects(rhs)
}

public func -<T: Hashable, S: SequenceType where S.Generator.Element == T> (lhs: OrderedSet<T>, rhs: S) -> OrderedSet<T> {
    var purgedSet = lhs
    purgedSet.removeObjects(rhs)

    return purgedSet
}

public func -=<T: Hashable, S: SequenceType where S.Generator.Element == T> (inout lhs: OrderedSet<T>, rhs: S) {
    lhs.removeObjects(rhs)
}

extension OrderedSet: Equatable { }

public func ==<T: Hashable> (lhs: OrderedSet<T>, rhs: OrderedSet<T>) -> Bool {
    if lhs.count != rhs.count {
        return false
    }

    for object in lhs {
        if lhs.contents[object] != rhs.contents[object] {
            return false
        }
    }

    return true
}
// ------------------------------------------------------------------------------------------


// finaly what do you want

var displayedExercises1 = OrderedSet<Exercise>()
let strengthExercises1 = OrderedSet<StrengthExercise>()
let cardioExercises1 = OrderedSet<CardioExercise>()
displayedExercises = strengthExercises

问题不在 @kylejm 的代码中,麻烦在于他的 OrderedSet 是不变的。我怀疑是因为 Swift 不支持非内置类型的类型变异。如果是这样的话,那就解释了为什么数组运作正常,它一定以某种方式特殊处理为协变。 - Nicholas H.
@user3441734 就像 Nicholas 所说的,您正在使用类型推断来实现此操作,但这与我在问题中的代码不匹配。如果您将结构体S的最后一个声明设置为采用符合协议P的类型关联而不是Base类,这将与OrderedSet<T: Hashable>相匹配,您将获得一个推断错误:无法推断泛型参数'T'。我已经制作了一个playground进行说明。其中还包含了我在问题中的代码。 - kylejs
你的 OrderedSet 是什么?在你的问题中,它是一个结构体。这是正确的吗,还是不正确? - user3441734
1
@user3441734,很抱歉你的解决方案仍然无法正常工作。你在最后一行打错了一个字,实际上是引用了代码片段顶部声明的两个“Set”。这里有一个示例来说明。你仍然会得到我在问题中提到的错误。 - kylejs
1
是的。也许,我会在几天内回来。如果我找到有用的东西,我会通知你们的……至少现在我们知道我们在谈论什么。 - user3441734
显示剩余19条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接