2015-11-17 146 views
6

考虑这些类:斯威夫特:投泛型类型为同一泛型类型,但与相关类型的子类

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>的期望。

的问题(S)

  • 为什么是必要的这个错误?
  • 如何写一些能够实现我要做的功能?



雷达协方差和逆变申请
rdar://23608799


博客文章
https://www.mikeash.com/pyblog/friday-qa-2015-11-20-covariance-and-contravariance.html

+2

这是一个协议(Exercisable?lol)比类层次更好的情况吗?或者也许类别/扩展? – Cocoadelica

+1

有趣的是:对于didSet:显示的练习函数,是否会将调用重新加载到调度中的tableview)调用异步到主队列或依靠记录接口来说你必须在主队列上设置值? – Cocoadelica

+0

@Cocoadelica当然!可爱的主意! ❤️它!将dispatch_async()添加到主队列中也是一个好主意。我更喜欢那种记录方式,因为那样只会写一次,而接近它的新开发人员不需要额外学习任何东西。也许你应该提供这个答案?如果没有人提供一个答案,也解释了为什么错误是必要的,我可以接受你的:) – kylejm

回答

6

我怕从Swift 2.1开始,它目前是不可能的。仅支持以下转换

  • 构建于集合类型中的元素类型是协变的。
  • 支持函数类型之间的转换,表现函数结果类型的协方差和函数参数类型的相反性。 (参看Xcode 7.1 Release Notes

由于Objective-C的泛型支持类型差异,并给出了雨燕2.1的功能类型转换方面取得的进展,我认为有理由相信型变异的支持将在被添加到斯威夫特未来。与此同时,请记住提交雷达,如jlieske has

同时您将不得不复制集合或使用其中一种内置集合类型。

更新,因为斯威夫特成为开源: 我相信Swift 3.0 Dev Roadmap完全仿制药部分指示类型变化将在3.0加以解决。虽然类型差异没有明确指出,但标准库中的特殊异常(包括类型差异)是。

+0

我想知道符合'CollectionType'是否会给这个功能?值得尝试? – kylejm

+0

@kylejm看起来你对'CollectionType'的支持,使得类型系统的特殊套用行为成为可能。 –

+0

@kylejm基于@ user3441734答案的讨论回滚了原始版本的答案,指出它毕竟不足以继承'CollectionType'。 –

2

由于OrderedSet<StrengthExercise>属于该特定类型,因此无法将其分配给更一般的OrderedSet<Exercise>。想一想,如果你试图在分配给该OrderedSet之后附加一个有氧运动,会发生什么。

答案可能是修改将力度练习的内容附加到练习集而不是分配整个类型集。

+0

我仍然不明白为什么将一个更具体的类型分配给更一般的类型在这里是一个问题。 OrderedSet是一个结构,所以它的价值传递?因此,如果您事先将'strengthExercises'赋值给'displayedExercises',则不会影响'strengthExercises'。更重要的是,您不能将'CardioExercise'附加到'displayedExercises',因为它的类型是OrderedSet '。 – kylejm

+0

我想我现在明白了!在给你写评论时,我意识到了这个问题!如果我将'Exercise'附加到'displayedExercises'并且它实际上包含'StrengthExercises'!这就是为什么你不能将'OrderedSet '分配给'displayedExercises'的原因!得到它了!谢谢! – kylejm

+0

关于第二个想法,我仍然不明白为什么这会是一个问题,因为'displayedExercises'会自己拥有'OrderedSet '副本,因为OrderedSet'是一个结构体(按值传递)。因此,将“Exercise”附加到它上面不会有问题,因为它会将“Exercise”附加到'strengthExercises'上? – kylejm

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 
+0

问题不在@ kylejm的代码中,问题在于它看起来他的OrderedSet是不变的。我怀疑是因为Swift不支持非内建类型的类型差异。如果这样可以解释为什么数组可以工作,它必须以某种方式特别协调才能协调。 –

+0

@NicholasH。我们对他的泛型OrderedSet一无所知...... – user3441734

+0

我不认为我们需要看到任何关于他的OrderedSet。这显然应该是一个通用的集合。事实上,类型系统抛出一个空洞的执行情况消除了他的实现作为一个因素。你的建议通过让类型推断成为's'和'sa'这两个'S '来解决这个问题,它不能为@ kylejm的问题提供一个合适的答案,因为'sa'实际上不是'S '。尝试在'let sa'之后加上':S '来查看问题,即它会出现* Swift泛型是不变的*(除了像Array这样的特殊的内置内置类型) –