Swift 字典如何用于存储通用对象 [T : C<T>]?

How can Swift Dictionaries be used to store generic objects [T : C<T>]?

我在使用 Swift 字典存储通用对象时遇到困难。

我正在尝试以下列方式存储对象:

[C: Container<C>], where C: ContentType

这样字典的结构如下:

Key        Value
---        -----
A          Container<A>
B          Container<B>
...

内容类型

我有一个名为 "ContentType" 的 class,它存储关于一种内容的元数据(即名称、大小、重量等)。

class ContentType: NSObject { ... }

我有许多 "ContentType" 的子class,每个子class 描述了一种特定类型的内容。

class ContentTypeA: ContentType { ... }
class ContentTypeB: ContentType { ... }

容器

我还有一个叫做"Container"的class,它被设计用来存储与内容类型相关的信息。适当地,它是一个泛型 class,其泛型类型为 "ContentType".

class Container<C: ContentType>: NSObject { }

我还在 "Container" 上实现了扩展,为特定 "ContentType" 定义了函数。

extension Container where C: ContentTypeA {
    func fancyFunctionOnlyFoundInContentTypeA() { }
}

extension Container where C: ContentTypeB {
    func fancyFunctionOnlyFoundInContentTypeB() { }
}

控制器

在我的主控制器class中,我实现了以下下标重载器。

subscript<C: ContentType>(contentType: C) -> Container<C> {
    // If Container for ContentType "C" does not exist
    if (self.containers[contentType] == nil) {
        // Then create it
        self.containers[contentType] = (Container<C>.init() as! Container<ContentType>)
    }
    // And return it
    return self.containers[contentType]! as! Container<C>
}

我们的想法是,您可以 obtain/reuse 一个特定 "ContentType" 的 "Container",它会准备好返回。

// Define a ContentType singletons somewhere
static let ContentTypeASingleton = ContentTypeA.init()
static let ContentTypeBSingleton = ContentTypeA.init()
...
// Using the subscript, the Container for ContentTypeA/ContentTypeB can be obtained
let containerForTypeA = self[ContentTypeASingleton]
let containerForTypeB = self[ContentTypeBSingleton]

从这里,可以调用特定于每个 ContentType 的容器扩展。

containerForTypeA.fancyFunctionOnlyFoundInContentTypeA()
containerForTypeB.fancyFunctionOnlyFoundInContentTypeB()

问题

我相信所有这些在理论上都应该有效,而且它确实可以在 Swift 4、Xcode 9.0 中编译。但是,执行以下命令后:

// Then create it
self.containers[contentType] = (Container<C>.init() as! Container<ContentType>)

我收到以下错误:

Could not cast value of type 'Test.Container<Test.ContentTypeA>' (0x109824a00) to 'Test.Container<Test.ContentType>' (0x109824638).

由于 ContentTypeA 继承自 ContentType,我不明白为什么这是个问题。似乎 Swift 字典无法存储类型为 ContentType < AnySubclassOfContentType > 的对象,必须将对象存储为 ContentType < ContentType > 。

有人有什么建议吗?干杯。

完整代码如下:

class ViewController: UIViewController {

    var containers = [ContentType : Container<ContentType>]()

    override func viewDidLoad() {
        super.viewDidLoad()

        let contentTypeA = ContentTypeA.init()
        let contentTypeB = ContentTypeB.init()

        let containerForTypeA = self[contentTypeA]
        let containerForTypeB = self[contentTypeB]

        containerForTypeA.fancyFunctionOnlyFoundInContentTypeA()
        containerForTypeB.fancyFunctionOnlyFoundInContentTypeB()
    }

    subscript<C: ContentType>(contentType: C) -> Container<C> {
        if (self.containers[contentType] == nil) {
            self.containers[contentType] = (Container<C>.init() as! Container<ContentType>)
        }
        return self.containers[contentType]! as! Container<C>
    }

}

class ContentType: NSObject { }
class Container<C: ContentType>: NSObject { }
class ContentTypeA: ContentType { }
class ContentTypeB: ContentType { }

extension Container where C: ContentTypeA {
    func fancyFunctionOnlyFoundInContentTypeA() { }
}

extension Container where C: ContentTypeB {
    func fancyFunctionOnlyFoundInContentTypeB() { }
}

崩溃的发生是因为 Swift 泛型是不变的,因此 Container<B> 不是 Container<A> 的子 class,即使 B 是一个A 的 subclass,任何向下转换都会失败,强制转换会导致崩溃。

您可以通过在字典中使用 AnyObject 作为基础 class 来解决此问题,因为 Container 派生自 NSObject:

var containers = [ContentType : AnyObject]()

subscript<C: ContentType>(contentType: C) -> Container<C> {
    if let container = containers[contentType] {
        return container as! Container<C>
    } else {
        let container = Container<C>()
        containers[contentType] = container
        return container
    }
}