Получить имя класса объекта в виде строки в Swift

320

Получение имени класса объекта с Stringиспользованием:

object_getClassName(myViewController)

возвращает что-то вроде этого:

_TtC5AppName22CalendarViewController

Я ищу чистую версию "CalendarViewController". Как получить вместо этого очищенную строку имени класса?

Я нашел несколько попыток вопросов по этому поводу, но не фактический ответ. Разве это невозможно вообще?

Бернд
источник
в качестве альтернативы ... как будет выглядеть эффективная функция синтаксического анализа для этого?
Бернд
Если вы хотите проверить, принадлежит ли объект определенному классу, посмотрите этот ответ .
значение имеет значение

Ответы:

530

Строка из экземпляра :

String(describing: YourType.self)

Строка из вида :

String(describing: 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"

Проверено с class, structи enum.

Рудольф Адамкович
источник
4
Это действительно так? В соответствии с документацией для String для этого инициализатора «неопределенный результат автоматически предоставляется стандартной библиотекой Swift», если он не соответствует Streamable, или CustomStringConvertible, или CustomDebugStringConvertible. Так что, хотя он может отображать желаемое значение сейчас, будет ли он продолжать делать это в будущем?
Тод Каннингем,
3
Использование этого в Xcode 7.3.1 & Swift 2.2 приводит к следующему, что не является идеальным. '' let name = String (self) name String "<MyAppName.MyClassName: 0x ########>" ''
CodeBender
13
В Swift 3 правильный ответ - позвонить, String(describing: MyViewController.self)как отмечено в нескольких ответах ниже.
AmitaiB
10
Но он возвращает «MyViewController.TYPE»!
Оркенштейн
3
@ RudolfAdamkovič Да, это было бы правдой. Но я не люблю писать имя класса явно (то , что если я переименовать класс Fooв Foo2но создать класс в Fooдругом месте, это может тормоз код). В случае создания подклассов вы должны использовать либо один, type(of:)либо в Type.selfзависимости от того, что соответствует вашим потребностям. Вероятно type(of:), больше подходит для получения имени класса.
Мариан Черны
182

ОБНОВЛЕНО к SWIFT 5

Мы можем получить красивые описания имен типов, используя переменную экземпляра через Stringинициализатор, и создавать новые объекты определенного класса.

Как например print(String(describing: type(of: object))). Где objectможет быть переменная экземпляра, такая как массив, словарь, an Int, a NSDateи т. Д.

Поскольку NSObjectэто корневой класс большинства иерархий классов Objective C, вы можете попытаться создать расширение для NSObjectполучения имени класса каждого подкласса NSObject. Как это:

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

Или вы можете создать статическую функцию, чей параметр имеет тип Any(протокол, которому неявно соответствуют все типы) и возвращает имя класса как String. Как это:

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

    }
}

Кроме того, как только мы можем получить имя класса в виде String, мы можем создать новые объекты этого класса :

// 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)

Может быть, это помогает кому-то там!

mauricioconde
источник
2
Это должен быть принятый ответ. Спасибо, я искал способ напечатать имя класса без необходимости его создания и нашел ответ здесь. Спасибо, печать (String (BaseAsyncTask))
Бруно Коэльо
4
classNameAsStringможно упростить до className, ибо для этого «имени» подразумевается его тип. theClassNameэто аналог истории Facebook, которая изначально называется facebook.
DawnSong
"diccionary" ... итальянская версия словаря Свифта:]
Эндрю Кирна,
Это решение предваряя Projectname для каждого classTag, то есть ProjectTest.MyViewController. Как мне избавиться от этого названия проекта? Я хочу только имя класса.
Саззад Хисейн Хан
132

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
nahung89
источник
12
Любая идея, почему я получаю MyAppName.MyClassName из этого? Меня интересует только MyClassName
Эндрю
@ Андрей, не могли бы вы дать более конкретную информацию по вашему делу? Я использую это расширение во многих проектах, и все они отвечают как задумано.
nahung89
1
Хорошо, если я вызываю его из самого класса, он возвращает MyClassName, но если я обертываю его в метод, который возвращает строку имени класса, и вызываю его из другого класса, он возвращает MyAppName.MyClassName. При поиске я обнаружил, что все классы неявно пространств имен, так что если вы вызываете его вне вашего класса, вы получите MyAppName.MyClassName вместо MyClassName. Тогда вам нужно полагаться на манипуляции со строками, чтобы получить имя класса.
Андрей
1
очень полезное расширение
Hattori Hanzō
@ Андрей, не уверенный в текущей работе, но я помню, String (описывающий: type (of: self)) раньше возвращал ModuleName.ClassName в одной точке. Я разделился на основе. персонаж и выбрал последний объект.
BangOperator
31

Swift 3.0

String(describing: MyViewController.self)

тонкий
источник
2
На самом деле type(of: )это перегиб там, String(describing: MyViewController.self)хватит
Александр Васенин
2
Это создает для меня строку, такую ​​как <App_Name.ClassName: 0x79e14450>, что не является идеальным
Даг Амос
Не так ли String.init(describing: MyViewController.self)?
Юлиан Онофрей
2
@IulianOnofrei, вы можете сделать это, но это initне нужно.
Шим
Полностью избавьтесь от префикса AppName! Спасибо
yuchen
28

Я предлагаю такой подход ( очень 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
werediver
источник
Они изменили это в Swift 3.0. Теперь вы можете получить строку изtype(of: self)
Асад Али
Обновлено до Swift 3.
werediver
Я думаю, что это лучший способ получить имя типа: любой экземпляр класса, основанный на NSObject, enum, typealiase, функциях, свойствах var, и любые типы, даже константы. Для типов вы должны использовать .self. Например, typeName (Int.self), typeName (true.self). Также вам не нужно беспокоиться об имени проекта как о части (например, AppProj. [Typename]) Отлично!
David.Chu.ca
23

Если у вас есть тип Foo, следующий код даст вам "Foo"в Swift 3 и Swift 4:

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"

Эта type(of:)часть не нужна, если вы просто хотите "Foo".

sethfri
источник
21

В 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
McNight
источник
14

Чтобы получить имя класса Swift из объекта, например, для объекта var: SomeClass (), используйте

String(describing: type(of: object))

Чтобы получить имя класса Swift из типа класса, например SomeClass, используйте:

String(describing: SomeClass.self)

Вывод:

"SomeClass"

Гурджит Сингх
источник
13

Вы можете попробовать так:

self.classForCoder.description()
陈方涛
источник
Это прекрасно работает, поскольку включает в себя также полное «пространство имен» (соответствующий целевой префикс членства).
BonanzaDriver
Кстати, я также обнаружил, что использование «String (self)», где self - это экземпляр MyViewController, тоже работает ... он предоставляет ту же информацию ... Xcode 7.1.
BonanzaDriver
12

Вы можете использовать стандартную библиотечную функцию Swift, которая называется _stdlib_getDemangledTypeNameтак:

let name = _stdlib_getDemangledTypeName(myViewController)
Йерней Страснер
источник
@NeilJaff Что вы подразумеваете под "просто необязательной строкой"? Возвращает строку и это не обязательно.
Джерней Страснер
3
Этот подход не возвращает имена классов в частных API FYI. Он вернет имя первого публичного имени базового класса.
Tylerc230
Я получаю сообщение: использование неразрешенного идентификатора '_stdlib_getDemangledTypeName'
Adobels,
12

Чтобы получить имя типа в виде строки в Swift 4 (я не проверял более ранние версии), просто используйте интерполяцию строк:

"\(type(of: myViewController))"

Вы можете использовать .selfдля самого типа, а type(of:_)функцию для экземпляра:

// Both constants will have "UIViewController" as their value
let stringFromType = "\(UIViewController.self)"
let stringFromInstance = "\(type(of: UIViewController()))"
Миша Карпенко
источник
8

Можно также использовать зеркала:

let vc = UIViewController()

String(Mirror(reflecting: vc).subjectType)

NB. Этот метод также можно использовать для структур и перечислений. Существует displayStyle, который дает представление о том, какой тип структуры:

Mirror(reflecting: vc).displayStyle

Возврат является перечислением, поэтому вы можете:

Mirror(reflecting: vc).displayStyle == .Class
Пол Арделеану
источник
Спасибо. Вот что сработало для меня. Единственное (по крайней мере, в iOS 9) это то, что subjectType заканчивался на «.Type», поэтому мне пришлось удалить эту часть из строки.
Николай Суванджиев
8

Swift 5.1

Вы можете получить имена классов, структур, перечислений, протоколов и NSObject Self.self.

print("\(Self.self)")
Виктор Кушнеров
источник
Обратите внимание, что это может предшествовать имени модуля (в отличие от String(describing: type(of: self)))
Ixx
7

Swift 3.0: вы можете создать расширение, подобное этому. Оно возвращает имя класса без имени проекта.

extension NSObject {
    var className: String {
        return NSStringFromClass(self as! AnyClass).components(separatedBy: ".").last ?? ""
    }

    public class var className: String {
        return NSStringFromClass(self).components(separatedBy: ".").last ?? ""
    }
}
исключение
источник
Когда я пытаюсь использовать это, я получаю сообщение об ошибке: Не удалось привести значение типа «ProjectName.MyViewController» (0x1020409e8) к «Swift.AnyObject.Type» (0x7fb96fc0dec0).
tylerSF
6

Вы можете расширить NSObjectProtocolв Swift 4, как это:

import Foundation

extension NSObjectProtocol {

    var className: String {
        return String(describing: Self.self)
    }
}

Это сделает вычисляемую переменную classNameдоступной для ВСЕХ классов. С помощью этого внутри print()ин CalendarViewControllerнапечатает "CalendarViewController"в консоли.

Сиддхарт Бхатт
источник
4

Я искал этот ответ время от времени. Я использую GKStateMachine и хотел наблюдать за изменениями состояния и хотел простой способ увидеть только имя класса. Я не уверен, что это iOS 10 или Swift 2.3, но в этой среде следующее делает именно то, что я хочу:

let state:GKState?
print("Class Name: \(String(state.classForCoder)")

// Output:    
// Class Name: GKState
Трой
источник
4

Вы можете получить имя класса, сделав что-то вроде:

class Person {}
String(describing: Person.self)
Эль Мохамад
источник
3

Чтобы получить имя класса в виде String, объявите ваш класс следующим образом

@objc(YourClassName) class YourClassName{}

И получить имя класса, используя следующий синтаксис

NSStringFromClass(YourClassName)
Моин Уддин
источник
3

Попробуйте reflect().summaryна Class self или экземпляр dynamicType. Разверните необязательные параметры перед получением dynamicType, в противном случае dynamicType является необязательной оболочкой.

class SampleClass { class InnerClass{} }
let sampleClassName = reflect(SampleClass.self).summary;
let instance = SampleClass();
let instanceClassName = reflect(instance.dynamicType).summary;
let innerInstance = SampleClass.InnerClass();
let InnerInstanceClassName = reflect(innerInstance.dynamicType).summary.pathExtension;
let tupleArray = [(Int,[String:Int])]();
let tupleArrayTypeName = reflect(tupleArray.dynamicType).summary;

Сводка - это путь к классу с описанными родовыми типами. Чтобы получить простое имя класса из резюме, попробуйте этот метод.

func simpleClassName( complexClassName:String ) -> String {
    var result = complexClassName;
    var range = result.rangeOfString( "<" );
    if ( nil != range ) { result = result.substringToIndex( range!.startIndex ); }
    range = result.rangeOfString( "." );
    if ( nil != range ) { result = result.pathExtension; }
    return result;
}
drawnonward
источник
3

Вышеуказанные решения не работают для меня. Произведенные в основном проблемы упоминаются в нескольких комментариях:

MyAppName.ClassName

или

MyFrameWorkName.ClassName

Это решение работало на XCode 9, Swift 3.0:

Я назвал его classNameCleanedтак, чтобы к нему было проще получить доступ и не вступать в конфликт с будущими className()изменениями:

extension NSObject {

static var classNameCleaned : String {

    let className = self.className()
    if className.contains(".") {
        let namesArray = className.components(separatedBy: ".")
        return namesArray.last ?? className
    } else {
        return self.className()
    }

}

}

Использование:

NSViewController.classNameCleaned
MyCustomClass.classNameCleaned
Darkwonder
источник
2

Swift 3.0 (MacOS 10.10 и более поздние версии), вы можете получить его из className

self.className.components(separatedBy: ".").last!
Тхань Vũ Trần
источник
1
Насколько я могу судить, в классах Swift нет встроенного свойства className. Вы подкласс от чего-то?
Шим
@shim className объявлено в Foundation, но кажется, что оно доступно только в macOS 10.10 и более поздних версиях. Я только что отредактировал свой ответ.
Тхан Vũ Trần
1
Хм, это странно. Почему бы им не включить его в iOS? Также обратите внимание , что это метод экземпляра на NSObject developer.apple.com/reference/objectivec/nsobject/...
шайбы
2

Я пробовал type(of:...)в Playground с Swift 3. Это мой результат. Это версия формата кода.

print(String(describing: type(of: UIButton.self)))
print(String(describing: type(of: UIButton())))
UIButton.Type
UIButton
Lumialxk
источник
Бесполезно создавать экземпляр только для определения имени класса.
Sethfri
@sethfri Я использую это, чтобы определить динамический тип.
Lumialxk
Я до сих пор не понимаю, почему вам нужно создать экземпляр, чтобы получить информацию о типе класса
sethfri
2

Этот вид примера для класса вар. Не включайте название пакета.

extension NSObject {
    class var className: String {
        return "\(self)"
    }
}
Иван Ткаченко
источник
1

Если вам не нравится искаженное имя, вы можете диктовать свое собственное имя:

@objc(CalendarViewController) class CalendarViewController : UIViewController {
    // ...
}

Однако, в конечном счете, было бы лучше научиться разбирать искаженное имя. Формат является стандартным и значимым и не изменится.

матовый
источник
Нет, это не так, для моего класса он возвращает (ExistentialMetatype) - полагаться на недокументированное поведение во время выполнения всегда опасно, особенно для быстрой работы, поскольку он все еще находится на относительно ранней стадии.
superarts.org
1

Иногда другие решения дают бесполезное имя в зависимости от того, на какой объект вы пытаетесь взглянуть. В этом случае вы можете получить имя класса в виде строки, используя следующее.

String(cString: object_getClassName(Any!))

⌘ щелкните по функции в xcode, чтобы увидеть некоторые связанные методы, которые довольно полезны. или проверьте здесь https://developer.apple.com/reference/objectivec/objective_c_functions

боб
источник
1

Swift 5 Вы можете получить class_name в виде строки с помощью String (описывающей: Self.self)

print(String(describing: Self.self))
yasinkbas
источник
1

Swift 5.2:

String(describing: type(of: self))
Абдельазиз Эльрашед
источник
0

Я использую это в Swift 2.2

guard let currentController = UIApplication.topViewController() else { return }
currentController.classForCoder.description().componentsSeparatedByString(".").last!
Александр Хитев
источник
0

Swift 5.1: -

Вы также можете использовать обобщенную функцию для получения имени класса объекта в виде строки

struct GenericFunctions {
 static func className<T>(_ name: T) -> String {
        return "\(name)"
    }

}

Вызовите эту функцию с помощью следующего: -

let name = GenericFunctions.className(ViewController.self)

Удачного кодирования :)

Динеш Шарма
источник