【问题标题】:Can you specify a return type in Swift as being any collection of a particular type?你可以将 Swift 中的返回类型指定为特定类型的任何集合吗?
【发布时间】:2015-01-25 12:26:12
【问题描述】:

我已经实现了一个自定义队列对象,我想用它来将以前的项目存储在控制器中。我想将此类型用作控制器中的私有变量,并且仅将其作为简单的CollectionType 兼容对象公开给外部,以便客户端可以在不知道任何特定于类的详细信息的情况下迭代或索引该对象,例如作为clear() 函数。

Swift 协议不能是通用的,所以很遗憾我不能简单地定义一个 getter 来返回一个CollectionOf<Type>。我已经使用以下抽象基类实现了这种行为,并从中继承了我的集合,但我希望可能有更 Swift-y 和内置的方法来实现这一点,希望也不需要子类化:

class AnyCollectionOf<MemberT, IndexT: ForwardIndexType>: CollectionType {
    // Sequence Type
    func generate() -> GeneratorOf<MemberT> {
        fatalError("must override")
    }

    // Collection Type
    typealias Index = IndexT
    typealias Element = MemberT

    subscript (index: Index) -> Element {
        get {
            fatalError("must override")
        }
    }

    var startIndex: Index {
        get {
            fatalError("must override")
        }
    }

    var endIndex: Index {
        get {
            fatalError("must override")
        }
    }
}

【问题讨论】:

标签: generics swift protocols


【解决方案1】:

很遗憾,您的标题问题的答案是否定的:无法将 CollectionType 作为可用作变量或返回类型的独立类型进行陪审。

像 SequenceType 和 CollectionType 这样的协议要求实现它们的类提供类型别名来填充实现细节,例如元素类型,正如您在上面所做的那样。一旦协议添加了这些要求,它就再也不能用作独立类型。您只能根据符合它的特定类来声明接口。 (如果您尝试过解决此问题,您可能还记得看到关于“关联类型要求”的编译器错误不是很有帮助。)

这就是你不会写作的根本原因

func countItems(collection: CollectionType) -> Int { ... }

但必须改写

func countItems<T: CollectionType>(collection: T) -> Int { ... }

后一种形式确保编译器可以访问实现 CollectionType 协议的对象的实际类型 (T)。

但是,如果您从封装而不是继承的角度考虑,您尝试做的事情可能仍然会有更清晰的实现。您可以使用简单的包装器来阻止对核心 CollectionType 方法以外的所有内容的访问:

struct ShieldedCollection<UCT: CollectionType> : CollectionType
{
    private var underlying: UCT

    func generate() -> UCT.Generator { return underlying.generate() }
    subscript(index: UCT.Index) -> UCT.Generator.Element { return underlying[index] }
    var startIndex: UCT.Index { return underlying.startIndex }
    var endIndex: UCT.Index { return underlying.endIndex }
}

var foo = [1, 2, 3]
var shieldedFoo = ShieldedCollection(underlying: foo)

(这里,UCT = "基础集合类型"。)

ShieldedCollection 仍然具有所有常见的类型别名作为 CollectionType,但由于可以从上下文中推断出这些类型别名,因此您不必显式指定它们。

这种通用方法的缺陷(不幸的是它是一个相当大的缺陷)是底层类型仍然泄漏到 API 中。上例中shieldedFoo的类型是

ShieldedCollection<Array<Int>>

由于您的基础集合是一个自定义对象,即使客户端无法直接访问该类本身,它的名称仍可能会在 API 中泄漏。请注意,这不是功能问题,因为它不应该通过 ShieldedCollection 包装器访问底层对象。此外,消费者永远不必自己编写类型 - 他们只需将 previousItems() 的结果用作 CollectionType,编译器就会解开所有内容。

如果您真的想隐藏对底层集合类型的所有提及,您可以通过将 UCT 定义移动到 ShieldedCollection 中来编写上述包装器的特定任务模拟:

struct ShieldedCollection<T> : CollectionType    // Changed!
{
    typealias UCT = [T]                          // Added!

    private var underlying: UCT                  // Everything else identical

    func generate() -> UCT.Generator { return underlying.generate() }
    subscript(index: UCT.Index) -> UCT.Generator.Element { return underlying[index] }
    var startIndex: UCT.Index { return underlying.startIndex }
    var endIndex: UCT.Index { return underlying.endIndex }
}

var foo = [1, 2, 3]
var shieldedFoo = ShieldedCollection(underlying: foo)

在这里,您通过放弃完全的通用性来使返回类型整洁——此版本的 ShieldedCollection 仅适用于作为数组的底层 CollectionType。 (当然,您只需在 UCT 的 typealias 中替换您自己的自定义集合类型。)

【讨论】:

  • 这个答案的第一部分是我要找的,谢谢!我最终使用了来自 stackoverflow.com/questions/27082137/… 的 CollectionOf 接受的答案(上面的评论者提到这可能是重复的),因为我觉得它为我的控制器属性提供了最通用的返回类型,尽管它的空间效率略低比你的方法。
【解决方案2】:

例如,我有一个带有 2 个标签和 1 个图像视图的堆栈视图并成功使用它:

open var labels: [UILabel] {
        return views.filter({ $0 is UILabel }).flatMap({ $0 }) as! [UILabel]
    }

如果你想让它通用:filterflatMap(来自 Swift 4 的compactMap)和强制可选

【讨论】:

    【解决方案3】:

    从 Swift 5.1 开始,这终于可以通过 some 关键字实现:

    func upNext() -> some Collection { ... }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-07-28
      • 1970-01-01
      • 1970-01-01
      • 2012-09-28
      • 2016-09-10
      • 1970-01-01
      • 2014-09-11
      相关资源
      最近更新 更多