Отримайте ім'я класу об'єкта як рядка в Swift


320

Отримання імені класу об'єкта як Stringвикористання:

object_getClassName(myViewController)

повертає щось подібне:

_TtC5AppName22CalendarViewController

Я шукаю для чистої версії: "CalendarViewController". Як отримати замість нього очищений рядок імені класу?

Я знайшов кілька спроб запитань з цього приводу, але не справжню відповідь. Це взагалі неможливо?


альтернативно ... як виглядатиме ефективна функція парсера для цього?
Бернд

Якщо ви хочете перевірити, чи є об'єкт певного класу, дивіться цю відповідь .
сенс-питання

Відповіді:


529

Рядок із екземпляра :

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"!
orkenstein

3
@ RudolfAdamkovič Так, це було б правдою. Але я не люблю писати ім'я класу явно (то , що якщо я перейменувати клас Fooв Foo2але створити клас в Fooіншому місці, це може гальмо код). У разі підкласингу вам потрібно використовувати type(of:)або Type.selfзалежно від ваших потреб. Можливо, type(of:)це більше підходить для отримання назви класу.
Marián Černý

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)

Можливо, це допомагає комусь там !.


2
Це має бути прийнятою відповіддю. Дякую, я шукав спосіб надрукувати ім’я класу без необхідності інстанції, і знайшов відповідь тут. Подяки подруги (String (BaseAsyncTask))
Bruno Coelho

4
classNameAsStringможна спростити до className, оскільки "ім'я" передбачало його тип. theClassNameє подібним до історії фейсбуку, який названий thebookbook спочатку.
Світанок

"diccionary" ... італійська версія колекції словників Свіфта:]
Ендрю Кірна

Це рішення, передбачуючи ім'я проекту для кожного classTag, тобто ProjectTest.MyViewController. Як я можу позбутися назви цього проекту? Я хочу лише назву класу.
Саззад Хассейн Хан

132

Швидкий 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

12
Будь-яка ідея, чому я отримую MyAppName.MyClassName з цього? Мене цікавить лише MyClassName
Ендрю

@Andrew ви могли б дати більш конкретні у вашому випадку? Я використовую це розширення у багатьох проектах, і всі вони відповідають за призначенням.
nahung89

1
Добре, якщо я називаю його з самого класу, він повертає MyClassName, але якщо я обертаю це у методі, який повертає рядок імені класу та називаю його з іншого класу, він повертає MyAppName.MyClassName. Під час пошуку я бачив, що всі класи неявно розділені на імена, тому, якщо ви називатимете його поза своїм класом, ви отримаєте MyAppName.MyClassName замість MyClassName. Тоді вам доведеться розраховувати на маніпуляції з рядком, щоб отримати назву класу.
Андрій

1
дуже корисне розширення
Хатторі Ханцō

@Andrew, не впевнений у поточній роботі, але я пам’ятаю, 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! Дякую
липня

28

Я пропоную такий підхід ( дуже стрімкий ):

// 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, 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".


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

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, що ви маєте на увазі під "просто необов'язковою строкою"? Він повертає рядок і не є необов'язковим.
Jernej Strasner

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)

Примітка: Цей метод також може бути використаний для структур та переліків. Існує displayStyle, який вказує на тип структури:

Mirror(reflecting: vc).displayStyle

Повернення - перерахунок, тож ви можете:

Mirror(reflecting: vc).displayStyle == .Class

Дякую. Ось що для мене спрацювало. Єдине (щонайменше, на iOS 9) - це те, що предметType закінчувався на ".Type", тому мені довелося видалити цю частину з рядка.
Микола Суванджієв

8

Швидкий 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самостійно клас або екземпляр з динамічним типом. Розгорніть необов’язкові параметри перед тим, як отримати dinamiльний тип, інакше dinamiльний тип - це необов'язковий обгортку.

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;
}

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

2

Swift 3.0 (macOS 10.10 та новіших версій), ви можете отримати його від className

self.className.components(separatedBy: ".").last!

1
Наскільки я можу сказати, немає вбудованої властивості className у класах Swift. Ви з чогось підкласу?
шим

@shim className оголошено у Foundation, але, здається, він доступний лише на macOS 10.10 та пізніших версіях. Я щойно відредагував свою відповідь.
Thanh Vũ Trần

1
Гм, це дивно. Чому б вони не включили його в iOS? Також зауважте, що це метод екземпляра на NSObject developer.apple.com/reference/objectivec/nsobject/…
shim

2

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

print(String(describing: type(of: UIButton.self)))
print(String(describing: type(of: UIButton())))
UIButton.Type
UIButton

Марно інстанціювати екземпляр просто для визначення назви класу.
sethfri

@sethfri Я використовую це для визначення динамічного типу.
Lumialxk

Я досі не розумію, чому вам потрібно створити екземпляр просто для отримання інформації про тип класу
sethfri


2

Цей вид прикладу для класу var. Не включайте ім'я групи.

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

0

Я використовую його в Swift 2.2

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

0

Швидкий 5.1: -

Ви також можете використовувати загальну функцію для отримання назви класу об'єкта як рядка

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

}

Викличте цю функцію, використовуючи наступне: -

let name = GenericFunctions.className(ViewController.self)

Щасливе кодування :)


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.