Swift
NSObject
description method
iOS development
Objective-C to Swift

What is the Swift equivalent of -NSObject description?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

The Swift equivalent of Objective-C's -[NSObject description] is the CustomStringConvertible protocol. Implementing its description property gives your type a custom text representation used by print(), string interpolation, and the debugger. For debug-only output, use CustomDebugStringConvertible with the debugDescription property, which mirrors Objective-C's -debugDescription.

CustomStringConvertible

swift
1struct User: CustomStringConvertible {
2    let name: String
3    let age: Int
4
5    var description: String {
6        return "User(name: \(name), age: \(age))"
7    }
8}
9
10let user = User(name: "Alice", age: 30)
11print(user)            // User(name: Alice, age: 30)
12print("Found: \(user)") // Found: User(name: Alice, age: 30)
13String(describing: user) // "User(name: Alice, age: 30)"

Without CustomStringConvertible, print(user) outputs User(name: "Alice", age: 30) using Swift's default reflection — which is often sufficient for structs but unhelpful for classes.

CustomDebugStringConvertible

swift
1struct Point: CustomStringConvertible, CustomDebugStringConvertible {
2    let x: Double
3    let y: Double
4
5    // Used by print(), String(describing:), string interpolation
6    var description: String {
7        return "(\(x), \(y))"
8    }
9
10    // Used by debugPrint(), po in LLDB, String(reflecting:)
11    var debugDescription: String {
12        return "Point(x: \(x), y: \(y))"
13    }
14}
15
16let p = Point(x: 3.0, y: 4.0)
17print(p)       // (3.0, 4.0)
18debugPrint(p)  // Point(x: 3.0, y: 4.0)
FunctionProtocol UsedObjective-C Equivalent
print(obj)CustomStringConvertibleNSLog(@"%@", obj) / -description
debugPrint(obj)CustomDebugStringConvertible-debugDescription
String(describing: obj)CustomStringConvertible[obj description]
String(reflecting: obj)CustomDebugStringConvertible[obj debugDescription]

NSObject Subclasses

For classes that inherit from NSObject, override the description property directly:

swift
1class Vehicle: NSObject {
2    let make: String
3    let model: String
4
5    init(make: String, model: String) {
6        self.make = make
7        self.model = model
8    }
9
10    override var description: String {
11        return "\(make) \(model)"
12    }
13
14    override var debugDescription: String {
15        return "Vehicle(make: \(make), model: \(model), hash: \(hash))"
16    }
17}
18
19let car = Vehicle(make: "Toyota", model: "Camry")
20print(car)       // Toyota Camry
21debugPrint(car)  // Vehicle(make: Toyota, model: Camry, hash: ...)

NSObject already conforms to CustomStringConvertible and CustomDebugStringConvertible, so you override instead of conforming.

Enums

swift
1enum Direction: CustomStringConvertible {
2    case north, south, east, west
3
4    var description: String {
5        switch self {
6        case .north: return "North"
7        case .south: return "South"
8        case .east:  return "East"
9        case .west:  return "West"
10        }
11    }
12}
13
14print(Direction.north)  // North
15// Without CustomStringConvertible: "north" (lowercase)

Classes Without NSObject

swift
1class NetworkError: Error, CustomStringConvertible, CustomDebugStringConvertible {
2    let code: Int
3    let message: String
4
5    init(code: Int, message: String) {
6        self.code = code
7        self.message = message
8    }
9
10    var description: String {
11        return "Error \(code): \(message)"
12    }
13
14    var debugDescription: String {
15        return "NetworkError(code: \(code), message: \"\(message)\")"
16    }
17}
18
19let error = NetworkError(code: 404, message: "Not Found")
20print(error)       // Error 404: Not Found
21debugPrint(error)  // NetworkError(code: 404, message: "Not Found")

Using in Collections

When objects conform to CustomStringConvertible, they display nicely in arrays and dictionaries:

swift
1struct Task: CustomStringConvertible {
2    let title: String
3    let done: Bool
4
5    var description: String {
6        return "\(done ? "[x]" : "[ ]") \(title)"
7    }
8}
9
10let tasks = [
11    Task(title: "Write code", done: true),
12    Task(title: "Write tests", done: false),
13    Task(title: "Deploy", done: false)
14]
15
16print(tasks)
17// [[x] Write code, [ ] Write tests, [ ] Deploy]

LLDB Debugging

In Xcode's debugger (LLDB):

 
(lldb) po myObject         # Uses debugDescription (or description as fallback)
(lldb) p myObject           # Uses the type's debug representation
(lldb) expression print(myObject)  # Uses description

po (print object) uses debugDescription if available, falling back to description. This matches Objective-C's behavior where po calls -debugDescription.

Mirror API (Reflection)

For automatic property listing without manual description:

swift
1struct Config {
2    let host: String
3    let port: Int
4    let ssl: Bool
5}
6
7let config = Config(host: "localhost", port: 8080, ssl: true)
8
9// Swift's Mirror gives you reflection
10let mirror = Mirror(reflecting: config)
11for child in mirror.children {
12    print("\(child.label ?? ""): \(child.value)")
13}
14// host: localhost
15// port: 8080
16// ssl: true

Structs get automatic reflection-based output from print(). Classes do not — they print the class name and memory address unless you implement description.

Common Pitfalls

  • Forgetting CustomStringConvertible for classes: Structs get a reasonable default print() output. Classes without CustomStringConvertible print as ClassName or a memory address. Always implement description for classes.
  • Using description in debugDescription: Avoid return description in debugDescription — they serve different purposes. description is user-facing; debugDescription should include type name and internal state for debugging.
  • String interpolation calls description: "\(myObject)" calls description, not debugDescription. If you need the debug version in a string, use String(reflecting: myObject).
  • NSObject subclasses: Override description as a computed property (override var description: String), not as a method. Swift properties and Objective-C methods bridge automatically.
  • Expensive descriptions: description may be called frequently (logging, debugging). Avoid expensive computations like network calls or database queries in the description getter.

Summary

  • CustomStringConvertible with description is the Swift equivalent of -[NSObject description]
  • CustomDebugStringConvertible with debugDescription is the equivalent of -[NSObject debugDescription]
  • print() uses description; debugPrint() and LLDB's po use debugDescription
  • NSObject subclasses override description directly; pure Swift types conform to the protocol
  • Use String(describing:) for user-facing strings and String(reflecting:) for debug output

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions