在Swift中将对象的类名作为字符串获取

432

使用以下方法将对象的类名获取为 String

object_getClassName(myViewController)

返回类似于这样的内容:

_TtC5AppName22CalendarViewController

我正在寻找纯净的版本:"CalendarViewController"。那么如何获取一个清理过的类名字符串呢?

我找到了一些关于这个问题的尝试,但没有实际的答案。难道完全不可能吗?


或者说,一个有效的解析器函数应该是什么样子的? - Bernd
如果您想检查一个对象是否属于某个类,请参见此答案 - meaning-matters
32个回答

695

实例中的字符串:

String(describing: self)

从一个类型获取字符串:

String(describing: YourType.self)

示例:

struct Foo {

    // Instance Level
    var typeName: String {
        return String(describing: Foo.self)
    }

    // Instance Level - Alternative Way
    var otherTypeName: String {
        let thisType = type(of: self)
        return String(describing: thisType)
    }

    // Type Level
    static var typeName: String {
        return String(describing: self)
    }

}

Foo().typeName       // = "Foo"
Foo().otherTypeName  // = "Foo"
Foo.typeName         // = "Foo"

使用classstructenum进行测试。


7
真的吗?根据String文档中这个初始化器的说明,“当不符合Streamable、CustomStringConvertible或CustomDebugStringConvertible时,Swift标准库会自动提供未指定结果”。因此,虽然它现在可能显示了所需的值,但将来是否仍然如此尚不确定。 - Tod Cunningham
4
在 Xcode 7.3.1 和 Swift 2.2 中使用这段代码会产生以下结果,这并不理想:"let name = String(self) name String "<MyAppName.MyClassName: 0x########>"。 - CodeBender
14
在Swift 3中,正确的答案是调用String(describing: MyViewController.self),就像下面几个回答中指出的那样。 - AmitaiB
12
但它返回的是"MyViewController.TYPE"! - orkenstein
4
是的,这是正确的。但我不喜欢明确地写出类名(如果我将类Foo重命名为Foo2,但在别处创建了一个类Foo,那么代码可能会出错)。在子类化的情况下,你需要使用type(of:)或者Type.self,具体取决于你的需求。可能更适合获取类名的是type(of:) - Marián Černý
显示剩余8条评论

230

更新至Swift 5

我们可以通过使用实例变量和String初始化器,获得类型名称的漂亮描述,并创建某个类的新对象

例如: print(String(describing: type(of: object)))。 这里的object可以是一个数组、一个字典、一个Int、一个NSDate实例变量

因为NSObject是大多数Objective-C类层次结构的根类,你可以尝试为NSObject创建一个扩展,以获取每个NSObject子类的类名。像这样:

extension NSObject {
    var theClassName: String {
        return NSStringFromClass(type(of: self))
    }
}

或者您可以创建一个静态函数,其参数的类型为Any(所有类型隐式符合的协议),并返回类名作为字符串。像这样:

或者你可以创建一个静态函数,它的参数类型是Any(所有类型都隐式地符合的协议),并返回类名作为字符串。就像这样:

class Utility{
    class func classNameAsString(_ obj: Any) -> String {
        //prints more readable results for dictionaries, arrays, Int, etc
        return String(describing: type(of: obj))
    }
} 

现在你可以像这样做:

class ClassOne : UIViewController{ /* some code here */ }
class ClassTwo : ClassOne{ /* some code here */ }

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Get the class name as String
        let dictionary: [String: CGFloat] = [:]
        let array: [Int] = []
        let int = 9
        let numFloat: CGFloat = 3.0
        let numDouble: Double = 1.0
        let classOne = ClassOne()
        let classTwo: ClassTwo? = ClassTwo()
        let now = NSDate()
        let lbl = UILabel()

        print("dictionary: [String: CGFloat] = [:] -> \(Utility.classNameAsString(dictionary))")
        print("array: [Int] = [] -> \(Utility.classNameAsString(array))")
        print("int = 9 -> \(Utility.classNameAsString(int))")
        print("numFloat: CGFloat = 3.0 -> \(Utility.classNameAsString(numFloat))")
        print("numDouble: Double = 1.0 -> \(Utility.classNameAsString(numDouble))")
        print("classOne = ClassOne() -> \((ClassOne).self)") //we use the Extension
        if classTwo != nil {
            print("classTwo: ClassTwo? = ClassTwo() -> \(Utility.classNameAsString(classTwo!))") //now we can use a Forced-Value Expression and unwrap the value
        }
        print("now = Date() -> \(Utility.classNameAsString(now))")
        print("lbl = UILabel() -> \(String(describing: type(of: lbl)))") // we use the String initializer directly

    }
}

此外,一旦我们能够将类名作为字符串获取,我们就可以实例化该类的新对象

// Instantiate a class from a String
print("\nInstantiate a class from a String")
let aClassName = classOne.theClassName
let aClassType = NSClassFromString(aClassName) as! NSObject.Type
let instance = aClassType.init() // we create a new object
print(String(cString: class_getName(type(of: instance))))
print(instance.self is ClassOne)

也许这能帮助到某个人!


5
classNameAsString 可以简化为 className,因为 "name" 表示其类型。theClassName 类似于 Facebook 的故事,最初被命名为 thefacebook。 - DawnSong
"diccionary"...是Swift字典集合的意大利版本 :] - Andrew Kirna
这个解决方案为每个classTag添加了项目名称前缀,例如ProjectTest.MyViewController。我该如何去掉这个项目名称?我只想要类名。 - Sazzad Hissain Khan

162

Swift 5

这里是一个扩展,可将typeName作为变量获取(适用于值类型或引用类型)。

protocol NameDescribable {
    var typeName: String { get }
    static var typeName: String { get }
}

extension NameDescribable {
    var typeName: String {
        return String(describing: type(of: self))
    }

    static var typeName: String {
        return String(describing: self)
    }
}

使用方法:

// Extend with class/struct/enum...
extension NSObject: NameDescribable {}
extension Array: NameDescribable {}
extension UIBarStyle: NameDescribable { }

print(UITabBarController().typeName)
print(UINavigationController.typeName)
print([Int]().typeName)
print(UIBarStyle.typeName)

// Out put:
UITabBarController
UINavigationController
Array<Int>
UIBarStyle

15
为什么我会得到 MyAppName.MyClassName 的结果呢?我只关心 MyClassName。 - Andrew
@Andrew,你能在你的情况下提供更具体的信息吗?我在许多项目中都使用这个扩展程序,并且它们都按预期响应。 - nahung89
2
如果我从类本身调用它,它会返回MyClassName,但如果我将其包装在返回类名字符串的方法中,并从另一个类调用它,则会返回MyAppName.MyClassName。在搜索时,我发现所有类都隐式命名空间化,因此如果您要在类外部调用它,则会得到MyAppName.MyClassName而不是MyClassName。然后,您必须依赖于字符串操作来获取类名。 - Andrew
2
非常有用的扩展。 - Hattori Hanzō
@Andrew,我不确定当前的工作状态,但是我记得,String(describing: type(of: self))曾经返回过ModuleName.ClassName。我曾根据“.”字符进行分割并获取了最后一个对象。 - BangOperator

39

Swift 5.2:

String(describing: type(of: self))

34
我建议采用如下方法(非常Swifty):
// Swift 3
func typeName(_ some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(type(of: some))"
}

// Swift 2
func typeName(some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(some.dynamicType)"
}

它既不使用内省也不使用手动解缠(没有魔法!)。


这是一个演示:

// Swift 3

import class Foundation.NSObject

func typeName(_ some: Any) -> String {
    return (some is Any.Type) ? "\(some)" : "\(type(of: some))"
}

class GenericClass<T> {
    var x: T? = nil
}

protocol Proto1 {
    func f(x: Int) -> Int
}


@objc(ObjCClass1)
class Class1: NSObject, Proto1 {
    func f(x: Int) -> Int {
        return x
    }
}

struct Struct1 {
    var x: Int
}

enum Enum1 {
    case X
}

print(typeName(GenericClass<Int>.self)) // GenericClass<Int>
print(typeName(GenericClass<Int>()))  // GenericClass<Int>

print(typeName(Proto1.self)) // Proto1

print(typeName(Class1.self))   // Class1
print(typeName(Class1())) // Class1
print(typeName(Class1().f)) // (Int) -> Int

print(typeName(Struct1.self)) // Struct1
print(typeName(Struct1(x: 1))) // Struct1
print(typeName(Enum1.self)) // Enum1
print(typeName(Enum1.X)) // Enum1

它在Swift 3.0中被更改了。现在你可以从type(of: self)获取字符串。 - Asad Ali
已更新至Swift 3。 - werediver
我认为这是获取类型名称的最佳方式:任何基于NSObject的类、枚举、类型别名、函数、变量属性和任何类型,甚至常量的实例。对于类型,您必须使用.self。例如,typeName(Int.self),typeName(true.self)。您也不需要担心项目名称作为部分(例如AppProj.[typename])。太棒了! - David.Chu.ca

32

Swift 3.0

String(describing: MyViewController.self)


3
实际上,在那里使用 type(of:) 是过度设计了,String(describing: MyViewController.self) 就足够了。 - Alexander Vasenin
4
这为我创建了一个字符串,例如<App_Name.ClassName: 0x79e14450>,这并不理想。 - Doug Amos
难道不是 String.init(describing: MyViewController.self) 吗? - Iulian Onofrei
3
@IulianOnofrei,你可以这样做,但init是不必要的。 - shim
彻底摆脱AppName前缀!谢谢 - yuchen
使用type(of:)并非过度,而是更可取的。因为如果您不使用它,则会调用debugDescription属性。现在,如果您想要覆盖debugDescription并在其中显示类名,那么将导致堆栈溢出。 - Moose

29
如果您有一个类型为 Foo,以下代码将在 Swift 3 和 Swift 4 中返回"Foo"
let className = String(describing: Foo.self) // Gives you "Foo"

这里大部分答案的问题在于当你没有类型的任何实例时,它们会给出"Foo.Type"作为结果字符串,而你真正想要的只是"Foo"。以下代码会给出"Foo.Type",正如其他答案中提到的那样。
let className = String(describing: type(of: Foo.self)) // Gives you "Foo.Type"

如果您只想要"Foo",则type(of:)部分是不必要的。


25

Swift 4.1 和现在的 Swift 4.2 中:

import Foundation

class SomeClass {
    class InnerClass {
        let foo: Int

        init(foo: Int) {
            self.foo = foo
        }
    }

    let foo: Int

    init(foo: Int) {
        self.foo = foo
    }
}

class AnotherClass : NSObject {
    let foo: Int

    init(foo: Int) {
        self.foo = foo
        super.init()
    }
}

struct SomeStruct {
    let bar: Int

    init(bar: Int) {
        self.bar = bar
    }
}

let c = SomeClass(foo: 42)
let s = SomeStruct(bar: 1337)
let i = SomeClass.InnerClass(foo: 2018)
let a = AnotherClass(foo: 1<<8)
如果您周围没有实例:
String(describing: SomeClass.self) // Result: SomeClass
String(describing: SomeStruct.self) // Result: SomeStruct
String(describing: SomeClass.InnerClass.self) // Result: InnerClass
String(describing: AnotherClass.self) // Result: AnotherClass

如果你确实有一个实例:

String(describing: type(of: c)) // Result: SomeClass
String(describing: type(of: s)) // Result: SomeStruct
String(describing: type(of: i)) // Result: InnerClass
String(describing: type(of: a)) // Result: AnotherClass

24

Swift 5.1

通过Self.self,您可以获取类、结构体、枚举、协议和NSObject的名称。

print("\(Self.self)")

请注意,这可能会在模块名称前面添加(与 String(describing: type(of: self)) 不同) - User
是的,它看起来很简洁,但也在模块名称前添加了内容。我认为这是对问题的错误回答... - hkdalex
3
这在发布版本配置上不起作用,它将返回Self作为一个字符串。有什么好的解释为什么会发生这种情况吗? - Mihai Erős

20

要从对象获取Swift类的名称,例如对于var object: SomeClass(),使用

String(describing: type(of: object))

获取Swift类的名称,例如SomeClass,使用以下代码:

String(describing: SomeClass.self)

输出:

"SomeClass"


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