Uzyskaj nazwę klasy obiektu jako ciąg znaków w Swift

320

Uzyskiwanie nazwy klasy obiektu za Stringpomocą:

object_getClassName(myViewController)

zwraca coś takiego:

_TtC5AppName22CalendarViewController

Szukam do czystej wersji: "CalendarViewController". Jak mogę posprzątać zamiast ciąg nazwy klasy?

Znalazłem kilka prób zadawania pytań na ten temat, ale nie rzeczywistą odpowiedź. Czy to w ogóle nie jest możliwe?

Bernd
źródło
alternatywnie… jak wyglądałaby skuteczna funkcja parsera dla tego?
Bernd
Jeśli chcesz sprawdzić, czy obiekt należy do określonej klasy, zobacz tę odpowiedź .
znaczenie ma znaczenie

Odpowiedzi:

529

Ciąg z instancji :

String(describing: YourType.self)

Ciąg z typu :

String(describing: self)

Przykład:

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"

Testowane z class, structi enum.

Rudolf Adamkovič
źródło
4
Czy to naprawdę tak jest? Zgodnie z dokumentacją String dla tego inicjalizatora „nieokreślony wynik jest dostarczany automatycznie przez standardową bibliotekę Swift”, gdy nie jest on zgodny z Streamable, CustomStringConvertible lub CustomDebugStringConvertible. Więc chociaż może teraz wyświetlać żądaną wartość, czy będzie nadal to robić w przyszłości?
Tod Cunningham
3
Używanie tego w Xcode 7.3.1 i Swift 2.2 daje następujące, co nie jest idealne. '' let name = String (self) name String "<MyAppName.MyClassName: 0x ########>" ''
CodeBender
13
W Swift 3 prawidłową odpowiedzią jest połączenie, String(describing: MyViewController.self)jak zaznaczono w kilku odpowiedziach poniżej.
AmitaiB,
10
Ale zwraca „MyViewController.TYPE”!
orkenstein,
3
@ RudolfAdamkovič Tak, to prawda. Ale nie wiem jak pisać nazwę klasy jawnie (co Gdybym zmienić nazwę klasy Foodo Foo2jednak utworzyć klasę Foogdzie indziej, że hamulcowy może kod). W przypadku podklasy należy użyć jednej type(of:)lub w Type.selfzależności od potrzeb. Prawdopodobnie type(of:)jest bardziej odpowiedni do uzyskania nazwy klasy.
Marián Černý
182

AKTUALIZACJA DO SWIFT 5

Możemy uzyskać ładne opisy nazw typów za pomocą zmiennej instancji poprzez Stringinicjator i stworzyć nowe obiekty określonej klasy

Na przykład print(String(describing: type(of: object))). W przypadku, gdy objectmoże być przykład zmienne jak tablicy, słownika, co oznacza Int, A NSDate, itd.

Ponieważ NSObjectjest to klasa główna większości hierarchii klas Objective-C, możesz spróbować utworzyć rozszerzenie dla NSObjectuzyskania nazwy klasy każdej podklasy NSObject. Lubię to:

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

Możesz też utworzyć funkcję statyczną, której parametr jest typu Any(protokół, z którym domyślnie wszystkie typy się zgadzają) i zwraca nazwę klasy jako String. Lubię to:

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

Teraz możesz zrobić coś takiego:

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

    }
}

Ponadto, gdy będziemy mogli uzyskać nazwę klasy jako String, możemy utworzyć instancję nowych obiektów tej klasy :

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

Może to pomaga komuś tam!

mauricioconde
źródło
2
To powinna być zaakceptowana odpowiedź. Dzięki, szukałem sposobu na wydrukowanie nazwy klasy bez konieczności jej tworzenia i znalazłem odpowiedź tutaj. Dzięki drukuj (String (BaseAsyncTask))
Bruno Coelho
4
classNameAsStringmożna uprościć className, ponieważ „nazwa” implikuje jej typ. theClassNamejest podobna do historii na Facebooku, która pierwotnie nosi nazwę thefacebook.
DawnSong,
„diccionary” ... włoska wersja kolekcji słownika Swift:]
Andrew Kirna
To rozwiązanie poprzedza nazwę projektu dla każdego tagu klasy, tj ProjectTest.MyViewController. Jak pozbyć się nazwy tego projektu? Chcę tylko nazwę klasy.
Sazzad Hissain Khan
132

Szybki 5

Oto rozszerzenie, które można uzyskać typeNamejako zmienną (praca zarówno z typem wartości, jak i typem odniesienia).

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

Jak używać:

// 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
źródło
12
Wiesz, dlaczego otrzymuję z tego MyAppName.MyClassName? Interesuje mnie tylko MyClassName
Andrew,
@Andrew, czy możesz podać bardziej szczegółowe informacje w swojej sprawie? Używam tego rozszerzenia w wielu projektach i wszystkie odpowiadają zgodnie z przeznaczeniem.
nahung89
1
Cóż, jeśli wywołam ją z samej klasy, zwróci ona MyClassName, ale jeśli zapakuję to w metodę, która zwraca ciąg nazwy klasy i wywołam ją z innej klasy, zwróci MyAppName.MyClassName. Podczas wyszukiwania zauważyłem, że wszystkie klasy mają domyślnie przestrzeń nazw, więc jeśli chcesz wywoływać je poza klasą, zamiast MyClassName otrzymasz MyAppName.MyClassName. Następnie musisz polegać na manipulacji ciągiem, aby uzyskać nazwę klasy.
Andrew
1
bardzo przydatne rozszerzenie
Hattori Hanzō
@Andrew, nie jestem pewien, czy działa, ale pamiętam, że String (opisujący: type (of: self)) użył do zwrócenia ModuleName.ClassName w jednym punkcie. Podzieliłem na podstawie. i pobrał ostatni obiekt.
BangOperator,
31

Swift 3.0

String(describing: MyViewController.self)

szczupły
źródło
2
Właściwie type(of: )jest tam przesada, String(describing: MyViewController.self)wystarczy
Alexander Vasenin
2
To tworzy dla mnie ciąg taki jak <App_Name.ClassName: 0x79e14450>, co nie jest idealne
Doug Amos
Nie jest to String.init(describing: MyViewController.self)?
Iulian Onofrei
2
@IulianOnofrei, Możesz to zrobić, ale nie initjest to konieczne.
shim
Całkowicie pozbądź się prefiksu AppName! Dzięki
yuchen,
28

Proponuję takie podejście ( bardzo 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)"
}

Nie używa introspekcji ani ręcznego demontażu ( bez magii! ).


Oto demo:

// 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
wilkołak
źródło
Zmienili to w Swift 3.0. Teraz możesz pobrać ciąg znaków odtype(of: self)
Asad Ali,
Zaktualizowano do Swift 3.
werediver
Myślę, że to najlepszy sposób na uzyskanie nazwy typu: każda instancja klasy oparta na NSObject, wyliczeniu, typalia, funkcjach, właściwościach var i dowolnych typach, a nawet stałych. W przypadku typów musisz użyć .self. Na przykład typeName (Int.self), typeName (true.self). Nie musisz się też martwić o nazwę projektu jako część (np. AppProj. [Typename]) Świetnie!
David.Chu.ca
23

Jeśli wpiszesz Foo, "Foo"w Swift 3 i Swift 4 otrzymasz następujący kod :

let className = String(describing: Foo.self) // Gives you "Foo"

Problem z większością odpowiedzi tutaj jest taki, że podają one "Foo.Type"jako wynikowy ciąg, gdy nie masz żadnej instancji tego typu, gdy to, czego naprawdę chcesz, jest po prostu "Foo". Poniżej podano "Foo.Type", jak wspomniano w kilku innych odpowiedziach.

let className = String(describing: type(of: Foo.self)) // Gives you "Foo.Type"

Ta type(of:)część jest niepotrzebna, jeśli chcesz "Foo".

sethfri
źródło
21

W Swift 4.1, a teraz 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)

Jeśli nie masz instancji w pobliżu:

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

Jeśli masz instancję w pobliżu:

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
źródło
14

Aby uzyskać nazwę klasy Swift z obiektu, np. Dla var object: SomeClass (), użyj

String(describing: type(of: object))

Aby uzyskać nazwę klasy Swift z typu klasy, np. SomeClass, użyj:

String(describing: SomeClass.self)

Wynik:

„SomeClass”

Gurjit Singh
źródło
13

Możesz spróbować w ten sposób:

self.classForCoder.description()
陈方涛
źródło
Działa to świetnie, ponieważ zawiera również pełną „przestrzeń nazw” (odpowiedni przedrostek docelowego członkostwa).
BonanzaDriver
BTW, znalazłem również przy użyciu „String (self)”, gdzie self jest instancją MyViewController, działa również ... zapewnia te same informacje ... Xcode 7.1.
BonanzaDriver
12

Możesz użyć standardowej funkcji biblioteki Swift o nazwie _stdlib_getDemangledTypeName:

let name = _stdlib_getDemangledTypeName(myViewController)
Jernej Strasner
źródło
@NeilJaff co rozumiesz przez „tylko opcjonalny ciąg”? Zwraca ciąg znaków i nie jest opcjonalny.
Jernej Strasner
3
To podejście nie zwraca nazw klas w prywatnych interfejsach API FYI. Zwróci nazwę pierwszej publicznej klasy bazowej.
Tylerc230,
Dostaję wiadomość: użycie nierozstrzygniętego identyfikatora „_stdlib_getDemangledTypeName”
Adobels
12

Aby uzyskać nazwę typu jako ciąg w Swift 4 (nie sprawdzałem wcześniejszych wersji), po prostu użyj interpolacji ciągów:

"\(type(of: myViewController))"

Możesz użyć .selfna samym typie, a type(of:_)funkcję na instancji:

// Both constants will have "UIViewController" as their value
let stringFromType = "\(UIViewController.self)"
let stringFromInstance = "\(type(of: UIViewController()))"
Misza Karpenko
źródło
8

Można również użyć serwerów lustrzanych:

let vc = UIViewController()

String(Mirror(reflecting: vc).subjectType)

Uwaga: Ta metoda może być również stosowana do Struktur i Wyliczeń. Istnieje displayStyle, który wskazuje, jaki typ struktury:

Mirror(reflecting: vc).displayStyle

Zwrot jest wyliczeniem, dzięki czemu możesz:

Mirror(reflecting: vc).displayStyle == .Class
Paul Ardeleanu
źródło
Dzięki. To działało dla mnie. Jedyną rzeczą (przynajmniej w iOS 9) jest to, że subjectType kończyło się na „.Type”, więc musiałem usunąć tę część z łańcucha.
Nikolay Suvandzhiev,
8

Swift 5.1

Możesz jednak uzyskać nazwy klas, struct, enum, protokół i NSObject Self.self.

print("\(Self.self)")
Victor Kushnerov
źródło
Pamiętaj, że może to poprzedzać nazwę modułu (inaczej niż String(describing: type(of: self)))
Ixx
7

Swift 3.0: Możesz utworzyć rozszerzenie takie jak to. Zwraca nazwę klasy bez nazwy projektu

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

    public class var className: String {
        return NSStringFromClass(self).components(separatedBy: ".").last ?? ""
    }
}
Wyjątek
źródło
Podczas próby użycia tego pojawia się błąd: Nie można rzutować wartości typu „ProjectName.MyViewController” (0x1020409e8) na „Swift.AnyObject.Type” (0x7fb96fc0dec0).
tylerSF
6

Możesz rozszerzyć NSObjectProtocolSwift 4 w następujący sposób:

import Foundation

extension NSObjectProtocol {

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

Dzięki temu zmienna obliczona będzie classNamedostępna dla WSZYSTKICH klas. Użycie tego w print()in CalendarViewControllerspowoduje wydrukowanie "CalendarViewController"w konsoli.

Siddharth Bhatt
źródło
4

Od jakiegoś czasu szukam tej odpowiedzi. Używam GKStateMachine i lubię obserwować zmiany stanu i chciałem w łatwy sposób zobaczyć tylko nazwę klasy. Nie jestem pewien, czy to tylko iOS 10 czy Swift 2.3, ale w tym środowisku następujące czynności są dokładnie takie, jak chcę:

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

// Output:    
// Class Name: GKState
Troja
źródło
4

Możesz uzyskać nazwę klasy robiąc coś takiego:

class Person {}
String(describing: Person.self)
Ale Mohamad
źródło
3

Aby uzyskać nazwę klasy jako String, zadeklaruj swoją klasę w następujący sposób

@objc(YourClassName) class YourClassName{}

I uzyskaj nazwę klasy, używając następującej składni

NSStringFromClass(YourClassName)
Moin Uddin
źródło
3

Spróbuj reflect().summaryna Class self lub instance dynamicType. Rozpakuj opcje przed uzyskaniem dynamicType, w przeciwnym razie dynamicType jest Opcjonalnym opakowaniem.

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;

Podsumowanie jest ścieżką klasy z opisanymi typami rodzajowymi. Aby uzyskać prostą nazwę klasy z podsumowania, wypróbuj tę metodę.

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;
}
pociągnięty
źródło
3

Powyższe rozwiązania nie działały dla mnie. Wytworzone głównie problemy wymieniane w kilku komentarzach:

MyAppName.ClassName

lub

MyFrameWorkName.ClassName

Te rozwiązania działały na XCode 9, Swift 3.0:

Nazwałem go, classNameCleanedaby był łatwiejszy w dostępie i nie powodował konfliktu z przyszłymi className()zmianami:

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

}

}

Stosowanie:

NSViewController.classNameCleaned
MyCustomClass.classNameCleaned
Darkwonder
źródło
2

Swift 3.0 (macOS 10.10 i nowsze wersje) można pobrać z className

self.className.components(separatedBy: ".").last!
Thanh Vũ Trần
źródło
1
O ile mogę stwierdzić, nie ma wbudowanej właściwości className w klasach Swift. Czy podklasowałeś z czegoś?
shim
@shim className jest zadeklarowany w Foundation, ale wygląda na to, że jest dostępny tylko w systemie macOS 10.10 i nowszych. Właśnie zredagowałem swoją odpowiedź.
Thanh Vũ Trần
1
Hm, to dziwne. Dlaczego nie mieliby tego uwzględniać w iOS? Zauważ też, że jest to metoda instancji na NSObject developer.apple.com/reference/objectivec/nsobject/…
shim
2

Próbowałem type(of:...)na Playground z Swift 3. To jest mój wynik. To jest wersja formatu kodu.

print(String(describing: type(of: UIButton.self)))
print(String(describing: type(of: UIButton())))
UIButton.Type
UIButton
Lumialxk
źródło
Nie ma sensu tworzyć instancji tylko po to, aby określić nazwę klasy.
sethfri
@sethfri Używam tego do określenia typu dynamicznego.
Lumialxk
Nadal nie rozumiem, dlaczego trzeba utworzyć instancję, aby uzyskać informacje o typie klasy
sethfri
2

Szybki 5

 NSStringFromClass(CustomClass.self)
Adobels
źródło
2

Ten rodzaj przykładu dla var var. Nie podawaj nazwy pakietu.

extension NSObject {
    class var className: String {
        return "\(self)"
    }
}
Ivan Tkachenko
źródło
1

Jeśli nie podoba Ci się zniekształcone imię, możesz podyktować swoje imię:

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

Jednak na dłuższą metę lepiej byłoby nauczyć się analizować zniekształconą nazwę. Format jest standardowy i znaczący i nie ulega zmianie.

matowy
źródło
Nie, nie jest, dla mojej klasy zwraca (ExistentialMetatype) - poleganie na nieudokumentowanym zachowaniu w środowisku wykonawczym jest zawsze niebezpieczne, szczególnie dla szybkiego, ponieważ wciąż jest na stosunkowo wczesnym etapie.
superarts.org,
1

Czasami inne rozwiązania podają nieprzydatną nazwę w zależności od obiektu, na który próbujesz spojrzeć. W takim przypadku możesz uzyskać nazwę klasy jako ciąg znaków, używając następującego.

String(cString: object_getClassName(Any!))

⌘ kliknij funkcję w xcode, aby zobaczyć niektóre pokrewne metody, które są dość przydatne. lub sprawdź tutaj https://developer.apple.com/reference/objectivec/objective_c_functions

kok
źródło
1

Swift 5 Możesz uzyskać nazwę_klasy jako ciąg znaków za pomocą ciągu znaków (opisującego: Self.self)

print(String(describing: Self.self))
yasinkbas
źródło
0

Używam go w Swift 2.2

guard let currentController = UIApplication.topViewController() else { return }
currentController.classForCoder.description().componentsSeparatedByString(".").last!
Alexander Chitev
źródło
0

Swift 5.1: -

Możesz także użyć funkcji ogólnej, aby uzyskać nazwę klasy obiektu jako ciąg znaków

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

}

Wywołaj tę funkcję, wykonując następujące czynności:

let name = GenericFunctions.className(ViewController.self)

Happy Coding :)

Dinesh Sharma
źródło
0

Swift 5.2:

String(describing: type(of: self))
Abdelaziz Elrashed
źródło