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

1

1

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'?

Lethbridge answered 11/1, 2019 at 6:44 Comment(0)
P
6

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.

Pooh answered 11/1, 2019 at 16:0 Comment(1)
Hey rob, Thanks a lot for the answer. It solved my issue. Tried the stuff you mentioned as side note also. Thanks for the additional info. Understood better how 'as' operator works! :)Lethbridge

© 2022 - 2024 — McMap. All rights reserved.