How do you inspect a class’s methods and properties at run time, and what does a Swift class show?

4.10.1 · L3 · Interview

Predict first

Which selectors does the runtime list for this class? (sorted)

final class Shape: NSObject {
    @objc var sides = 3
    @objc func area(scale: Double) -> Double { scale }
    @objc dynamic func name() -> String { "shape" }
    func hidden() {}
    var swiftOnly = 1
    private func secret() {}
}
var n: UInt32 = 0
let list = class_copyMethodList(Shape.self, &n)!
print((0..<Int(n)).map { NSStringFromSelector(method_getName(list[$0])) }.sorted())
  • ["area", "name", "sides"]
  • ["areaWithScale:", "init", "name", "setSides:", "sides"]
  • all seven members

Mental model. It is reading the phone book of a building: only departments that registered a number are listed, and a department with an internal extension never appears.

The runtime functions class_copyMethodList, class_copyPropertyList, class_copyIvarList and class_copyProtocolList return C arrays you free afterwards, and method_getName with NSStringFromSelector turns an entry into text. class_getSuperclass and object_getClass walk the hierarchy; the latter returns the real isa, which differs from the declared class for a KVO-observed object. Debuggers, serializers, mocking and “find the private method” investigations all use these calls.

From Swift you only see what was exposed to the runtime. For a Swift class that inherits NSObject, the list contains @objc members (methods with their Objective-C selector, such as areaWithScale:, plus accessors for @objc properties) and init, but not members that are plain Swift, private or generic. A pure Swift class without NSObject has no useful Objective-C metadata at all. The class name of a Swift class is module-qualified (Module.Shape), which matters when you look a class up by string.

The Swift label becomes part of the selector (areaWithScale:), a property gives a getter and a setter, and only @objc members appear.

Common traps

  • Forgetting to free the returned array. It leaks on every call.
  • Expecting to see Swift-only members. They are not in the method table, so absence is not proof a method does not exist.

They might follow up with

  • How do you find the real class of a KVO-observed object? object_getClass; type(of:) and .class may hide the subclass.
  • Is this safe to ship? Reading is, but calling private selectors found this way can be rejected in App Review.

Related questions

  • 4.1.4 What is isa, and why does your object’s runtime class sometimes differ from its Swift type?
  • 4.4.2 Which Swift features cannot be seen from Objective-C?
  • 4.4.1 What do @objc, @objcMembers, @objc(name), dynamic and @nonobjc each do?

What is a method type encoding, and why does a wrong signature crash… →