How to get the class name of an object in Swift: performance and best practices

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Why does a Swift developer need a class name as a string?

\n\n

Imagine a situation: you are developing logging for a complex application. Each event should contain not only the message but also the name of the class in which it occurred. Or you are building a universal JSON parser that needs to determine the model type by a string key. Or you are debugging a retain cycle and want to dynamically print all objects in memory with their class names.

\n\n

In all these cases, you need the same thing: get the class name of an object in Swift as a string. It seems simple, right? But it's not that straightforward. In Swift, unlike Objective-C, there is no single "magic" method that works perfectly in all situations. Moreover, different approaches yield different performance, and some even produce unexpected results.

\n\n

In this article, we will not just give a ready answer, but analyze all the ways to get a class name in Swift, compare their speed, and show when to use which method. You will learn how to avoid pitfalls and write code that won't let you down in production.

\n\n

The classic way: String(describing: type(of:))

\n\n

The most popular answer on Stack Overflow for this topic is to use a combination of type(of:) and String(describing:). Let's break down how it works.

\n\n
class Car {\n    var model: String\n    init(model: String) {\n        self.model = model\n    }\n}\n\nlet myCar = Car(model: "Tesla Model 3")\nlet className = String(describing: type(of: myCar))\nprint(className) // "Car"
\n\n

This method is indeed simple and intuitive. type(of:) returns the metatype of the object, and String(describing:) converts it to a string. But there is a nuance: it does not work correctly for optional types and protocols.

\n\n

Problem with optionals

\n\n
let optionalCar: Car? = Car(model: "BMW")\nlet optionalName = String(describing: type(of: optionalCar))\nprint(optionalName) // "Optional<Car>" — not what you expected!
\n\n

Instead of "Car", you get "Optional<Car>". This happens because type(of:) returns the type of the value itself, not its contents. For an optional, that is Optional<Car>.

\n\n

Problem with protocols and inheritance

\n\n
protocol Drivable { }\nextension Car: Drivable { }\n\nlet drivableObject: Drivable = Car(model: "Audi")\nlet protocolName = String(describing: type(of: drivableObject))\nprint(protocolName) // "Car" — lucky here, but not always
\n\n

With protocols, the result can be unpredictable, especially when using protocol composition or Any. Conclusion: this method is good for simple cases, but not for production code with optionals and complex types.

\n\n

Objective-C legacy: NSStringFromClass

\n\n

If you come from Objective-C, you probably remember NSStringFromClass([self class]). This also works in Swift, but with caveats:

\n\n
import Foundation\n\nclass Motorcycle {\n    var brand: String\n    init(brand: String) {\n        self.brand = brand\n    }\n}\n\nlet bike = Motorcycle(brand: "Harley")\nlet objcClassName = NSStringFromClass(type(of: bike))\nprint(objcClassName) // "_TtC11YourModule11Motorcycle" — mangled name!
\n\n

In Swift, classes have "mangled" names. Instead of "Motorcycle", you get something like _TtC11YourModule11Motorcycle. To get the clean name, you need to "demangle" it manually or use String(describing:).

\n\n

However, there is an important advantage: NSStringFromClass works with any objects, including optionals, and does not crash on nil. But for code clarity, it's better not to use it directly.

\n\n

When is NSStringFromClass still needed?

\n
    \n
  • When working with Obje

Blogs

Book Recommendations