泛型类型集合 [英] Generic Types Collection

查看:84
本文介绍了泛型类型集合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以先前解决的问题为基础,但这又导致了另一个问题.如果协议/类类型存储在集合中,则取回并实例化它们会引发错误.下面是一个假设的例子.该范例基于程序接口而不是实现".

Building on previous question which got resolved, but it led to another problem. If protocol/class types are stored in a collection, retrieving and instantiating them back throws an error. a hypothetical example is below. The paradigm is based on "Program to Interface not an implementation" What does it mean to "program to an interface"?

向协议进行实例化.在运行时动态引用类型

public protocol ISpeakable {
    init()
    func speak()
}

class Cat : ISpeakable {
    required init() {}
    func speak() {
        println("Meow");
    }
}

class Dog : ISpeakable {
    required init() {}
    func speak() {
        println("Woof");
    }
}

//Test class is not aware of the specific implementations of ISpeakable at compile time
class Test {
    func instantiateAndCallSpeak<T: ISpeakable>(Animal:T.Type) {
        let animal = Animal()
        animal.speak()
    }
}

// Users of the Test class are aware of the specific implementations at compile/runtime

//works
let t = Test()
t.instantiateAndCallSpeak(Cat.self)
t.instantiateAndCallSpeak(Dog.self)

//doesn't work if types are retrieved from a collection
//Uncomment to show Error - IAnimal.Type is not convertible to T.Type
var animals: [ISpeakable.Type] = [Cat.self, Dog.self, Cat.self]

for animal in animals {
    //t.instantiateAndCallSpeak(animal) //throws error
}

for (index:Int, value:ISpeakable.Type) in enumerate(animals) {
    //t.instantiateAndCallSpeak(value) //throws error
}

编辑-我当前的解决方法是遍历集合,但是由于api必须知道各种实现方式,因此这当然是有局限性的.另一个限制是这些类型的子类(例如PersianCat,GermanShepherd)将不会调用其重写的函数,否则我将进入Objective-C进行救援(NSClassFromString等),或者等待SWIFT支持此功能.

Edit - My current workaround to iterate through collection but of course it's limiting as the api has to know all sorts of implementations. The other limitation is subclasses of these types (for instance PersianCat, GermanShepherd) will not have their overridden functions called or I go to Objective-C for rescue (NSClassFromString etc.) or wait for SWIFT to support this feature.

注意(背景):实用程序的用户将这些类型推送到数组中,并在通知时执行for循环

Note (background): these types are pushed into array by users of the utility and for loop is executed on notification

var animals: [ISpeakable.Type] = [Cat.self, Dog.self, Cat.self]

for Animal in animals {
    if Animal is Cat.Type {
        if let AnimalClass = Animal as? Cat.Type {
            var instance = AnimalClass()
            instance.speak()
        }
    } else if Animal is Dog.Type {
        if let AnimalClass = Animal as? Dog.Type {
            var instance = AnimalClass()
            instance.speak()
        }
    }
}

推荐答案

基本上答案是:正确,您不能这样做. Swift需要在编译时而不是在运行时确定类型参数的具体类型.这在很多小的情况下都会出现.例如,您不能构造通用闭包并将其存储在没有类型指定的变量中.

Basically the answer is: correct, you can't do that. Swift needs to determine the concrete types of type parameters at compile time, not at runtime. This comes up in a lot of little corner cases. For instance, you can't construct a generic closure and store it in a variable without type-specifying it.

如果我们将其简化为最小的测试用例,则可能会更清楚

This can be a little clearer if we boil it down to a minimal test case

protocol Creatable { init() }

struct Object : Creatable { init() {} }

func instantiate<T: Creatable>(Thing: T.Type) -> T {
    return Thing()
}

// works. object is of type "Object"
let object = instantiate(Object.self)   // (1)

// 'Creatable.Type' is not convertible to 'T.Type'
let type: Creatable.Type = Object.self
let thing = instantiate(type)  // (2)

在第1行,编译器有一个问题:在instantiate的实例中,T应该是什么类型?这很容易,应该为Object.这是一个具体的类型,所以一切都很好.

At line 1, the compiler has a question: what type should T be in this instance of instantiate? And that's easy, it should be Object. That's a concrete type, so everything is fine.

在第2行,没有任何具体类型可以让Swift生成T.它所具有的只是Creatable,它是一种抽象类型(我们通过代码检查知道type的实际值,但是Swift并不考虑该值,而只是类型).可以接受和返回协议,但是不能将它们设置为类型参数.今天只是不合法的Swift.

At line 2, there's no concrete type that Swift can make T. All it has is Creatable, which is an abstract type (we know by code inspection the actual value of type, but Swift doesn't consider the value, just the type). It's ok to take and return protocols, but it's not ok to make them into type parameters. It's just not legal Swift today.

在声明泛型类型,函数或初始化程序时,请指定泛型类型,函数或初始化程序可以使用的类型参数.这些类型参数充当占位符,当创建泛型类型的实例或调用泛型函数或初始化程序时,这些实参将由实际具体类型参数替换. (重点是我的)

When you declare a generic type, function, or initializer, you specify the type parameters that the generic type, function, or initializer can work with. These type parameters act as placeholders that are replaced by actual concrete type arguments when an instance of a generic type is created or a generic function or initializer is called. (emphasis mine)

您需要在Swift中以其他方式做任何尝试.

You'll need to do whatever you're trying to do another way in Swift.

作为一项有趣的奖励,尝试明确要求不可能的事情:

As a fun bonus, try explicitly asking for the impossible:

let thing = instantiate(Creatable.self)

然后...迅速崩溃.

从您的进一步评论中,我认为闭包确实可以满足您的需求.您已使协议要求琐碎的构造(init()),但这是不必要的限制.您只需要调用者告诉函数如何构造对象.使用闭包很容易,并且完全不需要这种类型的参数化.这不是解决方法;我相信这是实现您所描述的模式的更好方法.考虑以下内容(一些小的更改,以使示例更像Swift):

From your further comments, I think closures do exactly what you're looking for. You've made your protocol require trivial construction (init()), but that's an unnecessary restriction. You just need the caller to tell the function how to construct the object. That's easy with a closure, and there is no need for type parameterization at all this way. This isn't a work-around; I believe this is the better way to implement that pattern you're describing. Consider the following (some minor changes to make the example more Swift-like):

// Removed init(). There's no need for it to be trivially creatable.
// Cocoa protocols that indicate a method generally end in "ing" 
// (NSCopying, NSCoding, NSLocking). They do not include "I"
public protocol Speaking {
    func speak()
}

// Converted these to structs since that's all that's required for
// this example, but it works as well for classes.
struct Cat : Speaking {
    func speak() {
        println("Meow");
    }
}

struct Dog : Speaking {
    func speak() {
        println("Woof");
    }
}

// Demonstrating a more complex object that is easy with closures,
// but hard with your original protocol
struct Person: Speaking {
    let name: String
    func speak() {
        println("My name is \(name)")
    }
}

// Removed Test class. There was no need for it in the example,
// but it works fine if you add it.
// You pass a closure that returns a Speaking. We don't care *how* it does
// that. It doesn't have to be by construction. It could return an existing one.
func instantiateAndCallSpeak(builder: () -> Speaking) {
    let animal = builder()
    animal.speak()
}

// Can call with an immediate form.
// Note that Cat and Dog are not created here. They are not created until builder()
// is called above. @autoclosure would avoid the braces, but I typically avoid it.
instantiateAndCallSpeak { Cat() }
instantiateAndCallSpeak { Dog() }

// Can put them in an array, though we do have to specify the type here. You could
// create a "typealias SpeakingBuilder = () -> Speaking" if that came up a lot.
// Again note that no Speaking objects are created here. These are closures that
// will generate objects when applied.
// Notice how easy it is to pass parameters here? These don't all have to have the
// same initializers.
let animalBuilders: [() -> Speaking] = [{ Cat() } , { Dog() }, { Person(name: "Rob") }]

for animal in animalBuilders {
    instantiateAndCallSpeak(animal)
}

这篇关于泛型类型集合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆