1

I am trying to get a list of classes that have adopted a certain Protocol Migration: Preparation, and then to append those classes into an array. Here is the function in question:

struct Migrations {

static func getMigrations() -> [Preparation.Type] {
    var migrationsList = [Preparation.Type]()
    var count = UInt32(0)
    let classList = objc_copyClassList(&count)!


    for i in 0..<Int(count) {
        let classInfo = ClassInfo(classList[i])!
        if let cls = classInfo.classObject as? Migration.Type {
            migrationsList.append(cls)
            print(cls.description)
        }
    }
    return migrationsList
}
}

In principle all that should work, but when debugging I note that the classInfo variable is referring to each class in the iteration, but when assigning and casting in the if let as line, the constant cls is always blank - neither a value/class nor nil, just completely blank.

Any idea what I got wrong with that code?

I am also open to suggestions for any better way to get a list of all classes that have adopted a particular protocol...

EDIT: I forgot to provide the code for ClassInfo

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
    }
}
cyehia
  • 79
  • 1
  • 6
  • So the code is making it to `migrationsList.append(cls)`? – allenh Jul 13 '17 at 22:54
  • So it's hard for me to tell exactly what is going on since I can't see with `ClassInfo` is, but I can tell you from experience that any time you have a variable containing a metatype, you won't be able to see it in lldb. Try printing it directly, or printing `type(of: thing)` – allenh Jul 13 '17 at 23:25
  • I added the `ClassInfo` code, sorry about that! As for your first question @AllenHumphreys, no unfortunately it never reaches that far. I made sure there is at least a class in my code that did adopt the `Migration` protocol, so it should have at least one hit... – cyehia Jul 13 '17 at 23:32

1 Answers1

0

I can't explain why cls is always blank, like I said in my comment it's something I run into every time I'm dealing with meta types. As for making the code work as intended, I found this q&a and updated it with Swift 3 to get this code which should cover your situation. It's important to stress that this will only work if you correctly expose Swift to the Objective-C runtime.

Drop this code anywhere and call print(Migrations.getMigrations()) from a convenient entry point.

struct Migrations {

    static func getMigrations() -> [Preparation.Type] {

        return getClassesImplementingProtocol(p: Preparation.self) as! [Preparation.Type]
    }

    static 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
    }

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

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

        allClasses.deallocate(capacity: Int(expectedClassCount))

        return classes
    }
}

class Migration: Preparation {

}

@objc
protocol Preparation {

}
allenh
  • 6,582
  • 2
  • 24
  • 40