我遇到了一个问题。 考虑我有一个协议和两个实现它的类:
protocol Initiatable{
init()
}
class A: Initiatable{
required init() {}
}
class B: Initiatable{
required init() {}
}
然后在某些时候我创建了一个数组并将其传递给函数:
var array = [A]()
func update(object: Any){
}
update(object: array)
来自该函数update
如果它符合另一个函数的条件,我想将object
传递给另一个函数:
func process<T: Initiatable>(array: T){
/* ... */
}
那么如何检查Any
类型的对象是否是实现Initiatable
协议的具体类的数组?我想写点像
func update(object: Any){
if let array = object as Array<T: Initiatable>{
process(array: array)
}
}
但那并不奏效。代码如:
func update(object: Any){
if let array = object as [Initiatable]{
process(array: array)
}
}
func process(array: [Initiatable]){ }
编译很好,但这不是我想要的 - process
函数应该收到Initiatable
的具体实现的数组,所以在某些时候它可以使用:
func process<T: Initiatable>(array: [T]){
/* other code */
T.init()
}
有没有办法做到这一点?非常感谢你提前!
答案 0 :(得分:3)
这个问题有几个部分:
生成类型数组
您的数组声明需要一组A
个对象而不是A
类型。要生成包含A类型的数组,您可以传入Postfix self
表达式:(link)
var array = [ A.self ]
这会将array
定义为A.Type
的数组,称为元类型类型(same link)。
您还可以使用此元类型类型生成空数组:
var array:[A.Type] = []
如果您想要一个同时包含A.self
和B.self
的数组,您可以将其指定为[Any]
...
var array:[Any] = [A.self,B.self]
...或者,使用您创建的Initiatable
协议:
var array:[Initiatable.Type] = [A.self,B.self]
在更新方法中将数组向下转换为类型数组
您无法将Any对象向下转换为类型数组。
这是我更新的update
方法:
func update(object: Any){
if let array = object as? [Initiatable.Type] { //1
process(array: array)
}
}
update
方法的数组执行可选的向下转换。将其向下转换为Initiatable
的元数据类型数组:(这是我从您的方法修改的唯一行)在流程方法中接收类型作为参数
我假设您只希望您的流程方法接收类型数组,并基于其中一种类型实例化变量。你没有提到数组中的哪个元素,所以我刚刚使用了第一个元素。
func process(array: [Initiatable.Type]){ //1
if let firstType = array.first { //2
let firstObject = firstType.init() //3
}
}
process
方法可以接收采用Initiatable协议的类型数组。答案 1 :(得分:1)
如果你的数组是非空的,那么你可以通过从数组中获取一个元素的运行时类型来解决这个问题:
func update(object: Any){
if let array = object as? [Initiatable]{
process(array: array)
} else {
// Not initiatable
}
}
func process(array: [Initiatable]) { // no need for generics here
guard let first = array.first else {
// Unable to determine array type
// throw or return or whatever
}
// type(of: first) gives A or B
type(of: first).init()
}
如果数组是空的,那么我不知道在Swift中这样做的方法(至于Swift 3,无论如何)。
object
的静态类型为Any
,因此您需要使用运行时类型的object
才能到达任何地方。T
。因此,使用process<T: Initiatable>(array: [T])
的整个方法都是死路一条:T
只能在调用process()
时使编译器已知的类型。type(of: object)
将返回(例如)Array<A>
。很近!但:
A
获取Array<A>
,除非编译器已经静态地知道类型参数。"Array<A>"
并从中提取字符串A
,但是AFAIK没有办法将字符串映射回Swift中的类型,除非类型是一个Objective-C类。简而言之,我认为你刚刚达到Swift的元类型/反射能力的极限。
答案 2 :(得分:0)
不幸的是,我无法在给定的约束条件下考虑解决方案。看起来你正试图在运行时实现编译时多态性。
使用泛型意味着编译器了解将调用哪个类。但是使用Any
意味着在编译时它可以是任何东西,只有在运行时才知道。
我可以提出的最接近的解决方案是:
// Either use generic `update` function
func updateGeneric<T: Initiatable>(array: Array<T>){
process(array: array)
}
// or explicitly convert `Any` to the class,
// so compiler understands what implementation of `process` to call
func updateExplicit(object: Any) {
if let array = object as? [A] {
process(array: array)
}
if let array = object as? [B] {
process(array: array)
}
}
答案 3 :(得分:0)
这对你有帮助吗,
protocol Initiatable{
init()
}
class A: Initiatable{
required init() {}
}
class B: Initiatable{
required init() {}
}
class Test<T : Initiatable>
{
func update(object: Any){
if let array = object as? [T]{
process(array: array)
}
}
func process(array: [T]){
}
}
答案 4 :(得分:0)
我找到了解决此问题的解决方法:
func classTypeFrom(_ className: String) -> AnyClass!{
if let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String {
let classStringName = "_TtC\(appName.characters.count)\(appName)\(className.characters.count)\(className)"
return NSClassFromString(classStringName)
}
return nil;
}
func update(object: Any){
if let array = object as? [Initiatable]{
let arrayTypeName = "\(type(of: ar))"
let objectTypeName = arrayTypeName.substringFrom(index: 6, length: arrayTypeName.characters.count - 7)
if let arrayType = classTypeFrom(objectTypeName) as? Initiatable.Type{
process(array: array, type: arrayType)
}
}
}
func process(array: [Initiatable], type: Initiatable.Type){
var ar = array
let newObj = type.init()
ar.append(newObj)
}