Cast to a Metatype Type in Swift?
First of all, as
takes a type, not an expression, on the right-hand side. So what you have is a syntax error.
What you seem to be trying to do is "cast" to a type that is computed at runtime. What would that even mean? Let's first consider what is a "cast".
Usually, when we have a cast expression x as T
, it has two components:
- At compile-time: The entire cast expression
x as T
has compile-time typeT?
, which allows you to do stuff with the resulting expression that you maybe cannot do onx
directly. In other words, it allows you to change the compile-time type. - At runtime: It checks whether the runtime type of
x
is a subtype ofT
, and if it is, it evaluates to the optional containing that value, otherwise, it evaluates tonil
.
If the type T
is not known at compile-time, then obviously you cannot do the compile-time part of it. (The compile-time type of the resulting expression cannot, obviously, depend on something which is not known at compile-time.)
The other part, the runtime component, could that be done with a type computed at runtime? Sure. For example,
import Foundation
let afterCast : Super? =
(superA as AnyObject).isKindOfClass(subType) ? superA : nil
It's not clear if that is what you want.
Storing and then casting to Metatypes in Swift
Ahmm. Very nice question. I had same 3-hours-pain few days ago. Finally, i found this answer, that main idea is:
Swift's static typing means the type of a variable must be known at
compile time.
So, the way you coding unfortunately (or fortunately :) ) not allowed.
Why does type(of:) return Metatype, rather than T.Type?
tl;dr: The behavior of type(of:)
depends on whether T
is existential or concrete, and the type system can't effectively reflect the actual return type syntactically, so it's handled directly in the type checking system. Metatype
is specifically not bound in code to be the same as T
so that the effective behavior can be specialized. Metatype
and T
are not necessarily related.
type(of:)
is special in that its behavior differs depending on the type passed into it. Specifically, it has special behavior for existential types by being able to reach through the existential box to get the underlying type of the value passed in. For example:
func myType<T>(of value: T) -> T.Type {
return T.self
}
protocol Foo {}
struct X: Foo {}
let x = X()
print(type(of: x), "vs.", myType(of: x)) // => X vs. X
let f: Foo = X()
print(type(of: f), "vs.", myType(of: f)) // => X vs. Foo
When given an existential type like Foo
, a return type of T.Type
could only return the metatype of the existential itself (i.e. Foo.self
), as opposed to the metatype of the value inside of the existential container (X.self
). So instead of returning T.Type
, type(of:)
returns an unrelated type Metadata
which is bound to the correct type in the type checker itself. This is the edge case you were looking for:
My guess is that there is some edge case where
type(of:)
will return a completely unrelated type toT
, but I have no idea what that is.
If you look in lib/Sema/TypeChecker.h
, you can see some special semantics declarations for several stdlib function types:
/// Special-case type checking semantics for certain declarations.
enum class DeclTypeCheckingSemantics {
/// A normal declaration.
Normal,
/// The type(of:) declaration, which performs a "dynamic type" operation,
/// with different behavior for existential and non-existential arguments.
TypeOf,
/// The withoutActuallyEscaping(_:do:) declaration, which makes a nonescaping
/// closure temporarily escapable.
WithoutActuallyEscaping,
/// The _openExistential(_:do:) declaration, which extracts the value inside
/// an existential and passes it as a value of its own dynamic type.
OpenExistential,
};
The key one here is TypeOf
, which is indeed returned for functions with the @_semantics("typechecker.type(of:)")
attribute you noted. (You can see how that attribute is checked in TypeChecker::getDeclTypeCheckingSemantics
)
If you go looking for usages of TypeOf
, there are two key locations in type-checking:
getTypeOfReferenceWithSpecialTypeCheckingSemantics
which injects the type constraint in the type checker constraint system.type(of:)
is handled here as an overload, becauseMetadata
isn't actually bound; the constraint solver here applies an effective type checking constraint which constrainsMetadata
to be the actual type ofvalue
. The key here is thattype(of:)
is written in this way so that it would be an overload, and handled here.ExprRewriter::finishApply
which performs the actual expression rewriting in the AST to replace the return type with the effective actual type of the value
From (1):
// Proceed with a "DynamicType" operation. This produces an existential
// metatype from existentials, or a concrete metatype from non-
// existentials (as seen from the current abstraction level), which can't
// be expressed in the type system currently.
Pulling back some history — this was implemented back in commit 1889fde2284916e2c368c9c7cc87906adae9155b. The commit message from Joe is illuminating:
Resolve
type(of:)
by overload resolution rather than parse hackery.
type(of:)
has behavior whose type isn't directly representable in Swift's type system, since it produces both concrete and existential metatypes. In Swift 3 we put in a parser hack to turntype(of: <expr>)
into a DynamicTypeExpr, but this effectively madetype(of:)
a reserved name. It's a bit more principled to putSwift.type(of:)
on the same level as other declarations, even with its special-case type system behavior, and we can do this by special-casing the type system we produce during overload resolution ifSwift.type(of:)
shows up in an overload set. This also lays groundwork for handling other declarations we want to ostensibly behave like normal declarations but with otherwise inexpressible types, viz.withoutActuallyEscaping
from SE-0110.
Since then, as we can see from WithoutActuallyEscaping
and OpenExistential
, other special functions have been rewritten to take advantage of this.
Why does Swift infer the metatype 'Any.Type' instead of Type 'Any'?
Why T
is not Any
This is simple. There is no ==
operator defined for Any?
and Any?
, as Any
is not Equatable
. T
cannot possibly be Any
.
Why T
can be Any.Type
This is also simple. There is such a built in operator that matches this signature exactly.
func == (t0: Any.Type?, t1: Any.Type?) -> Bool
Why it is not ambiguous
This is more subtle. You'd think that T
could be String
or Int
or Float
, right? After all, they all are Equatable
, and for all Equatable
types, Optional
defines a ==
and !=
for them. However, it seems like that operators declared in the global scope are considered "better" than operators declared in a conditional extension of Optional
, so operator overload resolution always chooses the non-extension operators first. This is reasonable - after all, adding extensions shouldn't cause old code to break.
As an example, adding this code would make your code produce an "ambiguous" error:
struct Foo {}
func ==(lhs: Foo?, rhs: Foo?) -> Bool {
true
}
func !=(lhs: Foo?, rhs: Foo?) -> Bool {
false
}
The newly added !=
operator for Foo?
and the built in operator for Any.Type?
are considered equally good candidates for the call giveMeZero() != nil
.
However, this would not:
struct Foo { }
extension Optional where Wrapped == Foo {
static func ==(lhs: Foo?, rhs: Foo?) -> Bool {
true
}
static func !=(lhs: Foo?, rhs: Foo?) -> Bool {
false
}
}
Can I cast a metaclass object to a protocol type in Swift?
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")
}
Swift 3. Cast Any to class which conforms specific protocol
You can test it as other type checking with if-let
:
protocol TestP {
init(param1: String)
}
class TestC {
var aClass: Any
init(_ aClass: Any) {
self.aClass = aClass
}
}
class MyClassA: TestP {
required init(param1: String) {
//
}
}
class MyClassB {
}
let containerA = TestC(MyClassA.self)
let containerB = TestC(MyClassB.self)
if let testPType = containerA.aClass as? TestP.Type {
var a = testPType.init(param1: "abc")
print(a) //->MyClassA
}
if let testPType = containerB.aClass as? TestP.Type {
print("This print statement is not executed")
}
By the way, if you assign only class types to aClass
, consider using AnyClass
or Any.Type
.
Checking if a metatype is an Array with an Element type of kind MyProtocol
The problem is that while instances of [MyDto]
can be freely converted to [MappableProtocol]
and [Any]
, these are really just magical conversions that the compiler does behind the scenes (see this Q&A for more information).
The same conversions don't exist for metatype values, which is why Swift says that a [MyDto].Type
is not a [MappableProtocol].Type
nor a [Any].Type
– they are unrelated metatype types.
Likely the simplest solution in your case would just be to forget working with metatypes, and instead just declare different overloads of test(a:)
to handle different ResponseType
types.
// 'default' overload of test(a:)
func test<T : ResponseProtocol>(a: T) -> String {
return "notFound"
}
func test<T : ResponseProtocol>(a: T) -> String where T.ResponseType == String {
return "String"
}
func test<T : ResponseProtocol>(a: T) -> String where T.ResponseType : MappableProtocol {
return "MappableProtocol"
}
func test<T : ResponseProtocol>(a: T) -> String where T.ResponseType == [MyDto] {
return "Array<MDto>"
}
// overload of test(a:) that accepts a type that conforms to ResponseProtocol, where the
// ResponseType is an Array with arbitrary Element type.
func test<T : ResponseProtocol, ResponseTypeElement : MappableProtocol>(a: T) -> String
where T.ResponseType == [ResponseTypeElement]
{
return "Array<MappableProtocol>"
}
print(test(a: A1())) // String
print(test(a: A2())) // MappableProtocol
print(test(a: A3())) // Array<MyDto>
// A MappableProtocol with a ResponseType that conforms to MappableProtocol,
// but isn't MyDto.
class Foo : MappableProtocol { required init(map: String) { } }
class A4 : ResponseProtocol { typealias ResponseType = [Foo] }
print(test(a: A4())) // Array<MappableProtocol>
(I removed your API
class just to simplify things)
The compiler will simply resolve which overload to call at compile time, rather than having the runtime jump through lots of type-casting hoops.
If you insist on working with metatype values, one possible solution is to define a dummy protocol for Array
to conform to (see for example this similar Q&A), which we can then cast the metatype values to. We can then declare an elementType
static requirement in order to extract the Array
's Element.self
metatype value, which we can then inspect the type of in order to determine what the array is convertible to.
For example, if we define and conform Array
to _ArrayProtocol
:
protocol _ArrayProtocol {
static var elementType: Any.Type { get }
}
extension Array : _ArrayProtocol {
static var elementType: Any.Type {
return Element.self
}
}
We can now use test(a:)
like so:
func test<T : ResponseProtocol>(a: T) -> String {
if T.ResponseType.self is String.Type {
return "String"
}
if T.ResponseType.self is MappableProtocol.Type {
return "MappableProtocol"
}
// attempt to cast the T.ResponseType.self metatype value to the existential metatype
// type _ArrayProtocol.Type (i.e a type that conforms to _ArrayProtocol),
// in this case, that's only ever Array.
if let responseType = T.ResponseType.self as? _ArrayProtocol.Type {
// switch on the element type, attempting to cast to different metatype types.
switch responseType.elementType {
case is MyDto.Type:
return "Array<MyDto>"
case is MappableProtocol.Type:
return "Array<MappableProtocol>"
default:
return "Array<Any>"
}
}
return "notFound"
}
print(test(a: A1())) // String
print(test(a: A2())) // MappableProtocol
print(test(a: A3())) // Array<MyDto>
print(test(a: A4())) // Array<MappableProtocol>
Related Topics
Hstack with Sf Symbols Image Not Aligned Centered
How to Create Text File for Writing
What Is the Role of Avcapturedevicetype.Builtindualcamera
Get Header Data from a Request Response in Swift
This Class Is Not Key Value Coding-Compliant for the Key Name.'
How to Use Dispatch Groups to Wait to Call Multiple Functions That Depend on Different Data
Swift "Where" Array Extensions
Why Does My Version of Filter Perform So Differently Than Swifts
Swift: How to Change a Property's Value Without Calling Its Didset Function
Cannot Read the Nfc Chip of the Epassport Using iOS13
Pass Optional Block or Closure to a Function in Swift
Nstextalignment.Justified for Uilabel Does Not Work
Firebase Sign Out Not Working in Swift
How Use and Run Swift 2.3 on Command Line
How to Add External .Vtt Subtitle File to Avplayerviewcontroller in Tvos