泛型类作为参数

3
我试图使用泛型来避免大量的强制类型转换。我将提供一个简单的例子:

我试图使用泛型来避免大量的强制类型转换。我将提供一个简单的例子:

import UIKit

class List<T> {
    let items: [T]

    init(items: [T]) {
        self.items = items
    }
}

class ListsViewController: UIViewController {
     var lists: [List<AnyObject>] = []
}

let viewController = ListsViewController()
let lists = [List(items: ["Fruits"])]
viewController.lists = lists
                       ^ error "String" is not identical to "AnyObject"

我读过一些关于如何做的文章

class ListsViewController<T>: UIViewController

但我会在链的深处遇到问题,那里我需要保存ListsViewController实例...所以这并没有帮助。

我不理解的是为什么示例可以使用默认的Swift数组:

import UIKit

class ListsViewController: UIViewController {
     var lists: [AnyObject] = []
}

let viewController = ListsViewController()
let lists = ["Fruits", "Meat"]
viewController.lists = lists

即使列表类型是[String]而不是[AnyObject],也不会出现错误。我能否在我的泛型类中获得相同的行为?

非常感谢您提前的帮助!


1
这是一篇关于逆变和协变的文章,可能会有所帮助。http://nomothetis.svbtle.com/type-variance-in-swift 许多语言允许数组是协变的(即使它不是类型安全的,并且需要运行时检查)。然而,正如你发现的那样,这种“宽容”并不总是适用于所有类。C#也有同样的问题(直到它添加了一种类型安全的实现协变的方式)。 - Michael Welch
这是我发布的链接中的一个简短相关段落:“话虽如此,我们开发人员能否声明类型为协变、逆变或不变?我看不出来。到目前为止,默认情况下内置的变异规则起作用(数组和字典是协变的,函数是……它们是什么,等等),但自定义类型都是不变的。” - Michael Welch
我明白了,太糟糕了 :( 不过还是谢谢你解决了这个问题! - warly
1个回答

4

更新 2016 年 8 月 22 日: 在评论中,@DeFrenZ 指出我的示例依赖于集合是引用类型。然而,在 Swift 中,集合是值类型。因此,如果您可以编译我的示例,我认为不会有任何问题。变量 listslists2 是独立的集合。修改一个集合不会影响另一个集合。

原始帖子如下:

除了我上面的评论和我分享的链接 http://nomothetis.svbtle.com/type-variance-in-swift(由 Alexandros Salazar 提供),值得指出的是,没有约束条件的集合的协变通常不是类型安全的。

这里是一个例子:

var lists: [List<AnyObject>] = []
var lists2: [List<String>] = List(items: ["Fruits"])
lists = lists2            // lists is now an alias for lists2 and is a reference to a list of strings
lists.append(MyObject())  // Now I just put a MyObject into a list of strings
var str:String = lists2[1] // assuming the previous lines compiled and ran ok, this would be a problem because lists2[1] is a MyObject. 

我正在凭记忆编写此语法,不太记得Swift将集合分配给变量时的实际语义,但希望这能指出问题。

3
这非常重要。这里的失败并不是Swift缺少功能,而是你所要求的不正确。你真正需要做的是在ListsViewController上添加一个类型参数,这样你就有了lists:[T]而不是lists:[AnyObject]。几乎每当你在属性中使用AnyObject时,你可能都在做一些不正确的事情。你应该深入挖掘为什么这会导致保存出问题。这可能是可以修复的。 - Rob Napier
很好的观点,@RobNapier。对于帮助他解决问题来说,可能比我在协变方面的评论更重要。 - Michael Welch
感谢您的所有解释。没错,在考虑了一下之后,我决定重构我的类,不再使用 AnyObject。虽然我不是语言专家,但我认为应该允许将已定义超类的子类放入集合中,并从那里继续进行。在您的示例中,“lists”将变成 [List<String>],因此 lists.append(MyObject()) 将失败。但这只是我的看法 :) - warly
@warly 这正是数组发生的情况。缺点有两个: 1)这可能是不安全的(如所示), 编译器没有任何提示, 2)检查数组中每个条目以确保其是正确类型需要运行时成本。(如果未来的Swift版本允许一些限制下的协变,例如不可变集合的协变是完全安全的,我也不会感到惊讶。) - Michael Welch
1
这个示例片段难道不是假设Array是引用类型吗?因为它们是值类型,而赋值lists = lists2不会保留引用,只会复制内容。当然还有另一个问题,即String is AnyObject == false。它应该是NSString is AnyObject == true - DeFrenZ
1
@DeFrenZ 我相信你是正确的。当我写下那句话的时候,也许我还不知道或者还没有“内化”Swift中集合是值类型的概念。 - Michael Welch

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