在 Swift 中在运行时创建 Objective-C 类的对象,符合 Objective-C 协议

tej*_*hri 1 objective-c objective-c-runtime swift

我有如下 Objective-C 协议和接口实现:

@protocol Animal <NSObject>
-(void)walk;
@end

@interface Cat : NSObject<Animal>
@end

@implementation Cat
-(void)walk{}
@end

@interface Dog : NSObject<Animal>
@end

@implementation Dog
-(void)walk{}
@end
Run Code Online (Sandbox Code Playgroud)

我试图在运行时使用实现协议“动物”的类的实例。此代码在 swift 中:

var classesCount = objc_getClassList(nil, 0)
let allClasses = UnsafeMutablePointer<AnyClass?>.allocate(capacity: Int(classesCount))
classesCount = objc_getClassList(AutoreleasingUnsafeMutablePointer(allClasses), classesCount)
for i in 0..<classesCount{
    let cls : AnyClass! = allClasses[Int(i)]
    if class_conformsToProtocol(cls, Animal.self){
        let instance = cls.self.init()
        instance.walk()
    }
}
Run Code Online (Sandbox Code Playgroud)

尝试了很多方法从 AnyClass、AnyObject 和 NSObject 中获取实例。我这样做时面临编译器错误。此代码片段的错误是:

“必需”初始化程序“init(arrayLiteral:)”必须由“NSSet”的子类提供。

有没有办法获得“猫”和“狗”的实例?

rob*_*off 6

让我们定义一个用于测试的noise方法Animal

@protocol Animal <NSObject>
- (NSString *)noise;
@end
Run Code Online (Sandbox Code Playgroud)

另外,让我们使用一个普通的 Swift 数组来保存类列表:

let allClassesCount = objc_getClassList(nil, 0)
var allClasses = [AnyClass](repeating: NSObject.self, count: Int(allClassesCount))
allClasses.withUnsafeMutableBufferPointer { buffer in
    let autoreleasingPointer = AutoreleasingUnsafeMutablePointer<AnyClass>(buffer.baseAddress)
    objc_getClassList(autoreleasingPointer, allClassesCount)
}
Run Code Online (Sandbox Code Playgroud)

然后,当我们找到一个符合 的类时Animal,让我们将其转换为合适的 Swift 类型 ( (NSObject & Animal).Type),这样当我们实例化它时,我们就会得到一个合适类型 ( NSObject & Animal)的对象:

for aClass in allClasses {
    if class_conformsToProtocol(aClass, Animal.self) {
        let animalClass = aClass as! (NSObject & Animal).Type

        // Because animalClass is `(NSObject & Animal).Type`:
        // - It has the `init()` of `NSObject`.
        // - Its instances are `NSObject & Animal`.
        let animal = animalClass.init()

        // Because animal is `NSObject & Animal`, it has the `noise` method of `Animal`.
        print(animal.noise())
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

woof
meow
Run Code Online (Sandbox Code Playgroud)

边注。您可能认为class_conformsToProtocol这样做可以避免使用:

if let animalClass = aClass as? (NSObject & Animal).Type {
    let animal = animalClass.init()
    print(animal.noise())
}
Run Code Online (Sandbox Code Playgroud)

但是你会在运行时崩溃:

*** CNZombie 3443: -[ conformsToProtocol:] sent to deallocated instance 0x7fffa9d265f0
Run Code Online (Sandbox Code Playgroud)

在内部,所述as?测试发送conformsToProtocol:消息以aClass使用正常的Objective-C消息。但是系统框架中存在各种“僵尸类”,当向它们发送任何消息时它们会崩溃。这些类用于检测释放后使用错误。该class_conformsToProtocol函数不使用 Objective-C 消息传递,因此可以避免这些崩溃。