Create object of Objective-C class at runtime in Swift, which conforms to Objective-C protocol

跟風遠走 提交于 2019-12-13 00:04:23

问题


I have Objective-C Protocol and Interface implementation as below:

@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

I am trying to use instance of classes at runtime which implement protocol 'Animal'. This code in in 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()
    }
}

Tried many ways to get instance from AnyClass, AnyObject, and NSObject. I am facing compiler errors in doing so. Error for this code snippet is:

'required' initializer 'init(arrayLiteral:)' must be provided by subclass of 'NSSet'.

Is there any way to get instances of 'Cat' and 'Dog'?


回答1:


Let's define a noise method on Animal for testing:

@protocol Animal <NSObject>
- (NSString *)noise;
@end

Also, let's use a normal Swift array to hold the class list:

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

Then, when we find a class that conforms to Animal, let's convert it to the appropriate Swift type ((NSObject & Animal).Type) so that when we instantiate it, we get an object of the appropriate 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())
    }
}

Output:

woof
meow

Side note. You might think you can avoid using class_conformsToProtocol by doing this:

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

But you will crash at runtime:

*** CNZombie 3443: -[ conformsToProtocol:] sent to deallocated instance 0x7fffa9d265f0

Under the covers, the as? test sends the conformsToProtocol: message to aClass using normal Objective-C messaging. But there are various “zombie classes” in the system frameworks that crash when any message is sent to them. These classes are used to detect use-after-free errors. The class_conformsToProtocol function doesn't use Objective-C messaging, so it avoids these crashes.



来源:https://stackoverflow.com/questions/54141572/create-object-of-objective-c-class-at-runtime-in-swift-which-conforms-to-object

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!