Swift
NSObject
description method
iOS development
Objective-C to Swift

What is the Swift equivalent of -NSObject description?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

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

Course illustration
Course illustration

All Rights Reserved.