Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I cast a metaclass object to a protocol type in Swift?

Swift inherited Objective-C's metaclass concept: classes themselves are also considered objects. A class Foo's object's class is Foo.self, and it is of type Foo.Type. If Foo inherits from Bar, then Foo.self can be assigned to a variable of type Bar.Type, too. This has at least two benefits:

  • it allows to override "static methods";
  • it's easy to create an instance of an unknown class in a type-safe way and without using reflection.

I'm particularly looking at the second one right now. Just to be sure that everybody understands what I'm after, here's an example:

class BaseFoo {
    var description: String { return "BaseFoo" }
}

class DerivedFoo: BaseFoo {
    override var description: String { return "DerivedFoo" }
}

let fooTypes: [BaseFoo.Type] = [BaseFoo.self, DerivedFoo.self] // metaclass magic!
for type in fooTypes {
    let object: BaseFoo = type() // metaclass magic!
    println(object)
}

Now, I have an array of AnyClass objects (any metaclass instance can be assigned to AnyClass, just like any object can be assigned to AnyObject), and I want to find which ones implement a given protocol. The protocol would declare an initializer, and I would instantiate the class just like I do in the example above. For instance:

protocol Foo {
    init(foo: String)
}

class Bar: Foo {
    required init(foo: String) { println("Bar initialized with \(foo)") }
}

class Baz {
    required init() { println("I'm not a Foo!") }
}

let types: [AnyClass] = [Bar.self, Baz.self]

So far so good. Now, the problem is determining if the class implements the protocol. Since metaclass instances are polymorphic, I'd expect to be able to cast them. However, I'm apparently missing something, because Swift won't let me write this:

for type in types {
    if let fooType = type as? Foo.Type {
        let obj = fooType(foo: "special snowflake string")
    }
}

The compiler error I get is:

error: 'Foo' is not identical to 'AnyObject'

Is there any way to determine if a metaclass instance represents a class that implements a protocol, and is there any way to cast that instance into a protocol type?

I tried to declare Foo as a class protocol, but it's apparently not enough.


EDIT: I just tried with the Any type, and while it doesn't cause a syntax error, it crashes the Swift compiler.

like image 265
zneak Avatar asked Dec 15 '14 18:12

zneak


1 Answers

As of Xcode 7 beta 2 and Swift 2 it has been fixed. You can now write:

for type in types {
    if let fooType = type as? Foo.Type {
        // in Swift 2 you have to explicitly call the initializer of metatypes
        let obj = fooType.init(foo: "special snowflake string")
    }
}

Or if you only want type as type Foo.Type you can use for case

for case let type as Foo.Type in types {
    let obj = type.init(foo: "special snowflake string")
}
like image 166
Qbyte Avatar answered Sep 19 '22 17:09

Qbyte