23

How to list all classes implementing a given protocol in Swift?

Say we have an example:

protocol Animal {
    func speak()
}

class Cat:Animal {
    func speak() {
        print("meow")
    }
}

class Dog: Animal {
    func speak() {
        print("Av Av!")
    }
}

class Horse: Animal {
    func speak() {
        print("Hurrrr")
    }
}

Here is my current (not compilable) approach:

func getClassesImplementingProtocol(p: Protocol) -> [AnyClass] {
    let classes = objc_getClassList()
    var ret = [AnyClass]()

    for cls in classes {
        if class_conformsToProtocol(cls, p) {
            ret.append(cls)
        }
    }
    return ret
}

func objc_getClassList() -> [AnyClass] {
    let expectedClassCount = objc_getClassList(nil, 0)
    let allClasses = UnsafeMutablePointer<AnyClass?>.alloc(Int(expectedClassCount))
    let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass?>(allClasses)
    let actualClassCount:Int32 = objc_getClassList(autoreleasingAllClasses, expectedClassCount)

    var classes = [AnyClass]()
    for i in 0 ..< actualClassCount {
        if let currentClass: AnyClass = allClasses[Int(i)] {
            classes.append(currentClass)
        }
    }

    allClasses.dealloc(Int(expectedClassCount))

    return classes
}

But when calling either

getClassesImplementingProtocol(Animal.Protocol) or

getClassesImplementingProtocol(Animal) or

getClassesImplementingProtocol(Animal.self)

results in Xcode error: cannot convert value of type (Animal.Protocol).Type to expected argument type 'Protocol'.

Did anyone manage get this working?

mixtly87
  • 1,675
  • 15
  • 32

2 Answers2

11

Since you're using the Objective-C runtime to get the type introspection you need to add @objc to your code in this manner:

@objc protocol Animal {
  func speak()
}

class Cat:Animal {
  @objc func speak() {
    print("meow")
  }
}

class Dog: Animal {
  @objc func speak() {
    print("Av Av!")
  }
}

class Horse: Animal {
  @objc func speak() {
    print("Hurrrr")
  }
}

Note that this kind of type introspection might be very slow.

  • Thanks Bruno, it works. Regarding performance, I just measured it: on iPad3 (which I regard as being among slow devices) it takes ca 115ms for a `getClassesImplementingProtocol` call, while on iPhone6S it takes 12ms. For me, this is executed once on app startup, so performance is not a critical issue. – mixtly87 Dec 22 '15 at 13:13
  • Not as slow as I expected, so that's a good thing! I'm glad it worked out for you. –  Dec 22 '15 at 13:14
  • 2
    Is there any way to get something similar with structures? – Francisco Medina May 26 '17 at 19:34
2

In terms of speed, you can do it in a single for, and avoid having to iterate through all classes like so:

func getClassesConformingProtocol(p: Protocol)-> [AnyClass]{
        let expectedClassCount = objc_getClassList(nil, 0)
        let allClasses = UnsafeMutablePointer<AnyClass>.allocate(capacity: Int(expectedClassCount))
        let autoreleasingAllClasses = AutoreleasingUnsafeMutablePointer<AnyClass>(allClasses)
        let actualClassCount:Int32 = objc_getClassList(autoreleasingAllClasses, expectedClassCount)

        var classes = [AnyClass]()
        for i in 0 ..< actualClassCount {
            let currentClass = allClasses[Int(i)]
            if class_conformsToProtocol(currentClass, p) {
                classes.append(currentClass)
            }
        }

        return classes

}
  • 2
    As a note, this way seems to no longer work as of Xcode 11.4. Seems that something is broken with the new SDK, and the classes that are returned from 'getClassList' is filled with invalid classes. https://bugs.swift.org/browse/SR-12344 – LowAmmo Mar 26 '20 at 20:50
  • 1
    Yup this was broken/reported in the 11.4-beta, and is still not fixed. Is something to do with swift retaining. The workaround also does not work for me, but don't be fooled, you can print the class with the workaround, but if you try add it to an array then the crash comes back....and then I cry a little. – Leslie Godwin May 15 '20 at 07:10