如何在Swift中列出符合协议的所有类?

23
如何在Swift中列出所有实现给定协议的类?假设我们有一个例子:
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")
    }
}

这是我的当前(无法编译的)方法:

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
}

但是调用以下任一语句:

getClassesImplementingProtocol(Animal.Protocol) 或者

getClassesImplementingProtocol(Animal) 或者

getClassesImplementingProtocol(Animal.self)

都会导致 Xcode 报错:“无法将类型 (Animal.Protocol).Type 转换为预期的参数类型 'Protocol'。”

是否有人成功解决了这个问题?


错误出现在哪一行? - luk2302
1
我认为这可能对你有所帮助:https://dev59.com/p14c5IYBdhLWcg3wQoY5 - Zell B.
2个回答

11

由于您正在使用Objective-C运行时来获取所需的类型内省,因此需要以以下方式向您的代码添加@objc

@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")
  }
}

请注意,这种类型的内省可能非常缓慢。

谢谢Bruno,它有效。关于性能,我刚刚测量了一下:在iPad3上(我认为它是较慢的设备之一),getClassesImplementingProtocol调用需要约115毫秒,而在iPhone6S上只需要12毫秒。对我来说,这个操作只在应用程序启动时执行一次,因此性能不是一个关键问题。 - mixtly87
没有我预期的那么慢,这是一件好事!我很高兴它对你有用。 - user887210
2
有没有办法用类似的结构来实现这个功能? - Francisco Medina

2

就速度而言,您可以在单个循环中完成操作,避免像这样迭代所有类:

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
作为一条注释,截至Xcode 11.4版本,这种方法似乎不再起作用。新的SDK似乎出现了一些问题,从'getClassList'返回的类被填充了无效的类。 https://bugs.swift.org/browse/SR-12344 - LowAmmo
1
是的,这个问题在11.4-beta版中出现了故障/报告,并且仍未修复。这与Swift保留有关。解决方法对我也不起作用,但不要被愚弄,你可以使用解决方法打印类,但如果你尝试将其添加到数组中,崩溃就会再次发生...然后我会哭一点。 - Leslie Godwin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接