Swift类内省和泛型

125
我正在尝试使用泛型动态创建一个基于类型的class实例,但是在类内省方面遇到了困难。
以下是问题:
  • 是否有Swift等价于Obj-C的self.class?
  • 是否有一种方法可以使用NSClassFromStringAnyClass结果来实例化一个类?
  • 是否有一种方法可以从泛型参数T中严格获取AnyClass或其他类型信息?(类似于C#的typeof(T)语法)

2
https://dev59.com/JGAg5IYBdhLWcg3wBXAR#24069875 给出了一些关于Swift反射API的提示。 - Klaas
5
在Swift中,Objective-C的self.class会变成self.dynamicType.self,我相信这样表达更加通俗易懂,但不改变原意。 - Filip Hermans
1
在实例方法中,这是如何调用类方法的:self.dynamicType.foo() - user246672
7个回答

109

首先,Swift中[NSString class]的等价语句是.self(请参见元类型文档,虽然它们非常简洁)。

实际上,NSString.class根本不起作用!您必须使用NSString.self

let s = NSString.self
var str = s()
str = "asdf"

同样地,我尝试了一个迅速的类...

class MyClass {

}

let MyClassRef = MyClass.self

// ERROR :(
let my_obj = MyClassRef()

嗯... 这个错误信息说:

Playground执行失败:错误::16:1:错误:使用元类型值构造一个类类型为'X'的对象需要一个'@required'初始化程序

 Y().me()
 ^
 <REPL>:3:7: note: selected implicit initializer with type '()'
 class X {
       ^
我花了一段时间才明白这是什么意思...结果发现它需要这个类有一个@required init()
class X {
    func me() {
        println("asdf")
    }

    required init () {

    }
}

let Y = X.self

// prints "asdf"
Y().me()

有些文档将其称为.Type,但在playground中,MyClass.Type会出错。


1
感谢您提供的Metatype文档链接!我完全忽略了Types的这个方面,真是糊涂! - Erik
14
你可以在变量声明中使用.Type.Protocol,例如:let myObject: MyObject.Type = MyObject.self - Sulthan
1
Sulthan: 所以MyObject.Type是一个声明,但MyObject.self是一个工厂方法(可以被调用),而myObject是一个包含对工厂方法的引用的变量。调用myObject()将产生一个MyObject类的实例。如果myObject变量的名称是myObjectFactory,那么这将是一个更好的示例。 - bootchk
2
required 前面的 @ 应该被删除。 - fujianjin6471

49

以下是如何使用NSClassFromString。你必须知道最终得到的类的超类。这里有一个超类和子类对,它们知道如何为println描述自己:

@objc(Zilk) class Zilk : NSObject {
    override var description : String {return "I am a Zilk"}
}

@objc(Zork) class Zork : Zilk {
    override var description : String {return "I am a Zork"}
}

请注意特殊的@obj语法的使用,用于指定这些类的Objective-C混淆名称;这是至关重要的,因为否则我们不知道指定每个类的混淆字符串。

现在我们可以使用NSClassFromString创建Zork类或Zilk类,因为我们知道我们可以将其类型定义为NSObject并且后续不会崩溃:

let aClass = NSClassFromString("Zork") as NSObject.Type
let anObject = aClass()
println(anObject) // "I am a Zork"

而且它是可逆的;println(NSStringFromClass(anObject.dynamicType))也可以工作。


需要翻译的内容:

And it's reversible; println(NSStringFromClass(anObject.dynamicType)) also works.

    if let aClass = NSClassFromString("Zork") as? NSObject.Type {
        let anObject = aClass.init()
        print(anObject) // "I am a Zork"
        print(NSStringFromClass(type(of:anObject))) // Zork
    }

10
顶一下@objc(ClassName)这一部分。我知道@objc属性,但不知道你还可以提示类名。 - Erik
1
优秀的解决方案,即使是6年后仍然基本保持原样。只有几个小调整需要进行:在第一行中添加 as! NSObject.Type,在第二行中添加 aClass.init() - Kaji

13

如果我正确阅读文档的话,假设您正在处理实例并且想返回与您获得的对象相同类型的新实例,并且该类型可以使用init()构造,则可以执行以下操作:

let typeOfObject = aGivenObject.dynamicType
var freshInstance = typeOfObject()

我用 String 快速测试了它:

let someType = "Fooo".dynamicType
let emptyString = someType()
let threeString = someType("Three")

运作良好。


1
没错,dynamicType 在那里的表现和我预期的一样。然而,我一直无法比较类型。真正大的用处是在泛型中,所以我可以有类似 Generic<T> 的东西,在内部使用 if T is Double {...}。不幸的是,这似乎是不可能的。 - Erik
1
@SiLo,你有没有找到一种通用的方法来判断两个对象是否属于同一个类? - matt
1
@matt 不太优雅,我没有。但是,我能够创建一个类似于C#的default关键字的Defaultable协议,并为诸如StringInt之类的类型添加适当的扩展。通过添加T:Defaultable的泛型约束,我可以检查传递的参数是否为T.default() - Erik
1
@SiLo聪明啊,我想看看那段代码!我猜这是绕过“is”使用的奇怪限制。我已经就这些限制提交了一个错误报告,还有关于缺乏类内省的问题。最后我用NSStringFromClass比较字符串,但当然这只适用于NSObject的子类。 - matt
1
很不幸,它听起来比实际情况聪明得多,因为你仍然需要执行 value is String.default()...等操作,而你最终只会执行 value is String - Erik

13

Swift 3

object.dynamicType

已被弃用。

请改用:

type(of:object)

7

Swift实现类型比较

protocol Decoratable{}
class A:Decoratable{}
class B:Decoratable{}
let object:AnyObject = A()
object.dynamicType is A.Type//true
object.dynamicType is B.Type//false
object.dynamicType is Decoratable.Type//true

注意: 请注意它也适用于对象可能扩展或不扩展的协议


1
这是另一个示例,展示了类层次结构的实现,类似于被接受的答案,更新为Swift的第一个版本。
class NamedItem : NSObject {
    func display() {
        println("display")
    }

    required override init() {
        super.init()
        println("base")
    }
}

class File : NamedItem {
    required init() {
        super.init()
        println("folder")
    }
}

class Folder : NamedItem {
    required init() {
        super.init()
        println("file")
    }
}

let y = Folder.self
y().display()
let z = File.self
z().display()

打印出这个结果:
base
file
display
base
folder
display

2
如果变量是超类的类型,则此技术无法正常工作。例如,给定var x:NamedItem.Type,如果我将其分配为x = Folder.Type,则x()返回一个新的NamedItem,而不是Folder。这使得该技术对许多应用程序无用。我认为这是一个错误 - phatmann
1
实际上,您可以使用此技术(https://dev59.com/b4Tba4cB1Zd3GeqP1yRS#26290606)来实现我认为您想要的功能。 - possen

1

终于让某些东西工作了。虽然有点懒,但即使使用NSClassFromString()方法也无法为我解决问题...

import Foundation

var classMap = Dictionary<String, AnyObject>()

func mapClass(name: String, constructor: AnyObject) -> ()
{
    classMap[name] = constructor;
}

class Factory
{
    class func create(className: String) -> AnyObject?
    {
        var something : AnyObject?

        var template : FactoryObject? = classMap[className] as? FactoryObject

        if (template)
        {
            let somethingElse : FactoryObject = template!.dynamicType()

            return somethingElse
        }

        return nil
    }
}


 import ObjectiveC

 class FactoryObject : NSObject
{
    @required init() {}
//...
}

class Foo : FactoryObject
{
    class override func initialize()
    {
        mapClass("LocalData", LocalData())
    }
    init () { super.init() }
}

var makeFoo : AnyObject? = Factory.create("Foo")

然后,“makeFoo”包含一个Foo实例。

缺点是你的类必须派生自FactoryObject,并且它们必须具有Obj-C +initialize方法,以便通过全局函数“mapClass”将您的类自动插入类映射中。


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