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

235

Получение имени класса в виде строки с помощью

object_getClassName(myViewController)

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

_TtC5AppName22CalendarViewController

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

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

  • 0
    в качестве альтернативы ... как будет выглядеть эффективная функция синтаксического анализа для этого?
Теги:

23 ответа

417

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

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. Так что, хотя он может отображать желаемое значение сейчас, будет ли он продолжать делать это в будущем?
  • 0
    Кажется, работает на детской площадке, см. I.imgur.com/nJzTrjQ.png
Показать ещё 11 комментариев
108

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

Мы можем получить красивые описания имен типов, используя переменную экземпляра через инициализатор 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 (протокол, которому неявно соответствуют все типы) и который возвращает имя класса в виде 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("diccionary: [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, мы можем создать новые объекты этого класса:

// Instanciate a class from a String
print("\nInstaciate 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)

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

  • 2
    Это должен быть принятый ответ. Спасибо, я искал способ напечатать имя класса без необходимости его создания и нашел ответ здесь. Спасибо, печать (String (BaseAsyncTask))
  • 2
    classNameAsString можно упростить до className , поскольку это «имя» подразумевает его тип. theClassName является theClassName истории Facebook, которая изначально называется facebook.
107

Swift 4.2

Вот расширение для получения 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 {}

print(UITabBarController().typeName)
print(UINavigationController.typeName)
print([Int]().typeName)
  • 9
    Любая идея, почему я получаю MyAppName.MyClassName из этого? Меня интересует только MyClassName
  • 0
    @ Андрей, не могли бы вы дать более конкретную информацию по вашему делу? Я использую это расширение во многих проектах, и все они отвечают как задумано.
Показать ещё 2 комментария
29

Swift 3.0

String(describing: MyViewController.self)

  • 2
    На самом деле type(of: ) String(describing: MyViewController.self) является излишним, String(describing: MyViewController.self) будет достаточно
  • 2
    Это создает строку, такую как <App_Name.ClassName: 0x79e14450> для меня, что не идеально
Показать ещё 3 комментария
27

Я предлагаю такой подход (очень 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
  • 0
    Они изменили это в Swift 3.0. Теперь вы можете получить строку из type(of: self)
  • 0
    Обновлено до Swift 3.
Показать ещё 1 комментарий
19

Если у вас есть тип 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".

14

В 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
12

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

let name = _stdlib_getDemangledTypeName(myViewController)
  • 0
    @NeilJaff Что вы подразумеваете под "просто необязательной строкой"? Возвращает строку, и это не обязательно.
  • 3
    Этот подход не возвращает имена классов в частных API FYI. Он вернет имя первого публичного имени базового класса.
11

Чтобы получить имя типа как строку в 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

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

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

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

let vc = UIViewController()

String(Mirror(reflecting: vc).subjectType)

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

Mirror(reflecting: vc).displayStyle

Возврат - это перечисление, чтобы вы могли:

Mirror(reflecting: vc).displayStyle == .Class
  • 0
    Благодарю. Вот что сработало для меня. Единственное (по крайней мере, в iOS 9) это то, что subjectType заканчивался на «.Type», поэтому мне пришлось удалить эту часть из строки.
6

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 ?? ""
    }
}
  • 0
    Когда я пытаюсь использовать это, я получаю сообщение об ошибке: Не удалось привести значение типа «ProjectName.MyViewController» (0x1020409e8) к «Swift.AnyObject.Type» (0x7fb96fc0dec0).
4

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

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

// Output:    
// Class Name: GKState
2

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

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
2

Попробуйте reflect().summary в классе 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;
}
1

Вы можете расширить NSObjectProtocol в Swift 4 следующим образом:

import Foundation

extension NSObjectProtocol {

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

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

1

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

print(String(describing: type(of: UIButton.self)))
print(String(describing: type(of: UIButton())))
UIButton.Type
UIButton
  • 0
    Бесполезно создавать экземпляр только для определения имени класса.
  • 0
    @sethfri Я использую это, чтобы определить динамический тип.
Показать ещё 1 комментарий
1

Swift 3.0 (macOS 10.10 и новее), вы можете получить его из классаName

self.className.components(separatedBy: ".").last!
  • 1
    Насколько я могу судить, в классах Swift нет встроенного свойства className. Вы подкласс от чего-то?
  • 0
    @shim className объявлено в Foundation, но кажется, что оно доступно только в macOS 10.10 и более поздних версиях. Я только что отредактировал свой ответ.
Показать ещё 1 комментарий
1

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

@objc(YourClassName) class YourClassName{}

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

NSStringFromClass(YourClassName)
0

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

String(cString: object_getClassName(Any!))

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

0

Я использую его в Swift 2.2

guard let currentController = UIApplication.topViewController() else { return }
currentController.classForCoder.description().componentsSeparatedByString(".").last!
0

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

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

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

  • 1
    И посмотрите также мой ответ здесь: stackoverflow.com/a/24196632/341994
  • 0
    Нет, это не так, для моего класса он возвращает (ExistentialMetatype) - полагаться на недокументированное поведение во время выполнения всегда опасно, особенно для быстрой работы, поскольку он все еще находится на относительно ранней стадии.
-2

В моем случае String (описывая: self) вернула что-то вроде:

< My_project.ExampleViewController: 0x10b2bb2b0 >

Но я бы хотел иметь что-то вроде getSimpleName на Android.

Итак, я создал небольшое расширение:

extension UIViewController {

    func getSimpleClassName() -> String {
        let describing = String(describing: self)
        if let dotIndex = describing.index(of: "."), let commaIndex = describing.index(of: ":") {
            let afterDotIndex = describing.index(after: dotIndex)
            if(afterDotIndex < commaIndex) {
                return String(describing[afterDotIndex ..< commaIndex])
            }
        }
        return describing
    }

}

И теперь он возвращает:

ExampleViewController

Расширение NSObject вместо UIViewController также должно работать. Функция выше также является отказоустойчивой:)

  • 0
    Это потому, что вы передаете экземпляр имени класса вместо класса, поэтому в приведенном выше примере вы должны передать ExampleViewController.self

Ещё вопросы

Сообщество Overcoder
Наверх
Меню