2015-11-17 147 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這樣的特殊的內置內置類型) –