List all subclasses of one class

Surprisingly the Objective-C runtime functions work just as well with Swift classes, even if they don't subclass from NSObject. Further, all classes in Swift seems to derive from SwiftObject. SwiftObject itself does not have a superclass.

First, a wrapper structure to handle the ObjC runtime functions:

import Foundation

struct ClassInfo : CustomStringConvertible, Equatable {
    let classObject: AnyClass
    let className: String

    init?(_ classObject: AnyClass?) {
        guard classObject != nil else { return nil }

        self.classObject = classObject!

        let cName = class_getName(classObject)!
        self.className = String(cString: cName)
    }

    var superclassInfo: ClassInfo? {
        let superclassObject: AnyClass? = class_getSuperclass(self.classObject)
        return ClassInfo(superclassObject)
    }

    var description: String {
        return self.className
    }

    static func ==(lhs: ClassInfo, rhs: ClassInfo) -> Bool {
        return lhs.className == rhs.className
    }
}

Here's how you can use it:

class Mother { }
class ChildFoo: Mother { }
class ChildBar: Mother { }

class AnIrrelevantClass { }

let motherClassInfo = ClassInfo(Mother.self)!
var subclassList = [ClassInfo]()

var count = UInt32(0)
let classList = objc_copyClassList(&count)!

for i in 0..<Int(count) {
    if let classInfo = ClassInfo(classList[i]),
        let superclassInfo = classInfo.superclassInfo,
        superclassInfo == motherClassInfo
    {
        subclassList.append(classInfo)
    }
}

print(subclassList)

This only performs a shallow search so it won't sweep up grandchildren classes but you get the idea.


An optimised version of Jean Le Moignan's code

    static func subclasses<T>(of theClass: T) -> [T] {
        var count: UInt32 = 0, result: [T] = []
        let allClasses = objc_copyClassList(&count)!
        let classPtr = address(of: theClass)

        for n in 0 ..< count {
            let someClass: AnyClass = allClasses[Int(n)]
            guard let someSuperClass = class_getSuperclass(someClass), address(of: someSuperClass) == classPtr else { continue }
            result.append(someClass as! T)
        }

        return result
    }
public func address(of object: Any?) -> UnsafeMutableRawPointer{
    return Unmanaged.passUnretained(object as AnyObject).toOpaque()
}

For every Type there is only one metaclass instance in runtime, so, pointers on them are unique. In some reason operator === is not allowed for AnyClass, but we can compare pointers directly

performance test:

        let start = CFAbsoluteTimeGetCurrent()
        let found = RuntimeUtils.subclasses(of:UIViewController.self)
        let diff = CFAbsoluteTimeGetCurrent() - start
        print("Took \(diff) seconds, \(found.count) found")

output:

String(describing: theClass): Took 1.0465459823608398 seconds, 174 found

address(of: theClass): Took 0.2642860412597656 seconds, 174 found


Here's a variant based on Code Different's previous answer, but shorter:

func subclasses<T>(of theClass: T) -> [T] {
    var count: UInt32 = 0, result: [T] = []
    let allClasses = objc_copyClassList(&count)!

    for n in 0 ..< count {
        let someClass: AnyClass = allClasses[Int(n)]
        guard let someSuperClass = class_getSuperclass(someClass), String(describing: someSuperClass) == String(describing: theClass) else { continue }
        result.append(someClass as! T)
    }

    return result
}

The return type will be according to the receiving variable type, thanks to generics.

I wanted to write this as an extension to AnyClass... but unfortunately Swift doesn't allow it.