Swift - what's the difference between metatype .Type and .self?
Here is a quick example:
func printType<T>(of type: T.Type) {
// or you could do "\(T.self)" directly and
// replace `type` parameter with an underscore
print("\(type)")
}
printType(of: Int.self) // this should print Swift.Int
func printInstanceDescription<T>(of instance: T) {
print("\(instance)")
}
printInstanceDescription(of: 42) // this should print 42
Let's say that each entity is represented by two things:
Type:
# entitiy name #
Metatype:
# entity name # .Type
A metatype type refers to the type of any type, including class types, structure types, enumeration types, and protocol types.
Source.
You can quickly notice that this is recursive and there can by types like (((T.Type).Type).Type)
and so on.
.Type
returns an instance of a metatype.
There are two ways we can get an instance of a metatype:
Call
.self
on a concrete type likeInt.self
which will create a
static metatype instanceInt.Type
.Get the dynamic metatype instance from any instance through
type(of: someInstance)
.
Dangerous area:
struct S {}
protocol P {}
print("\(type(of: S.self))") // S.Type
print("\(type(of: S.Type.self))") // S.Type.Type
print("\(type(of: P.self))") // P.Protocol
print("\(type(of: P.Type.self))") // P.Type.Protocol
.Protocol
is yet another metatype which only exisits in context of protocols. That said, there is no way how we can express that we want only P.Type
. This prevents all generic algorithms to work with protocol metatypes and can lead to runtime crashes.
For more curious people:
The type(of:)
function is actually handled by the compiler because of the inconsistency .Protocol
creates.
// This implementation is never used, since calls to `Swift.type(of:)` are
// resolved as a special case by the type checker.
public func type<T, Metatype>(of value: T) -> Metatype { ... }
Swift metatype (Type, self)
No, cls
and cls2
are different things. The easiest way to understand the difference will be to extend your example like this:
class SomeClass {
class func doIt() {
print("I'm a class method. I belong to my type.")
}
func doItOnlyIfInstanceOfThisType() {
print("I'm a instance method. I belong to my type instance.")
}
}
And now let's take your cls
:
let cls : SomeClass.Type = SomeClass.self
cls.doIt()
That will print I'm a class method. I belong to my type.
. But you cannot invoke this:
cls.doItOnlyIfInstanceOfThisType() // causes a compilation error, PRO TIP: actually you can use this method as a func property, but I'll add the explanation for this later
Let's take your cls2
. The only visible method of it is doItOnlyIfInstanceOfThisType
because it's an instance method (of this type).
let cls2 : SomeClass = SomeClass()
cls2.doItOnlyIfInstanceOfThisType()
So the difference between them is that cls
is a type and cls2
is an instance of this type.
A little bit more knowledge about why SomeClass.self and SomeClass()?
The type of a class also exists in memory (it has for example its own methods), as a singleton representing the Type (not an instance of this type - that's something different).
If you call self
on a Type like this SomeClass.self
you will get a singleton instance representing the SomeClass
Type.
SomeClass()
invokes the init()
method of SomeClass
, a constructor that creates an instance of SomeClass
.
PRO Tip
You can manipulate a Type instance function (like closures/blocks in ObjC). It's a generated class method. But you must pass an instance of the type that you take this method from as an argument, like this:
let myFunc :()->() = cls.doItOnlyIfInstanceOfThisType(cls2)
myFunc()
Swift get type from metatype
Assuming an array of NSManagedObject.Type's
var entities: [NSManagedObject.Type] {
return [Entity1.self,
Entity2.self]
}
for entity in entities {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = entity.fetchRequest()
do {
let result = try managedObjectContext.fetch(fetchRequest)
//do something with result
} catch {
print(error)
}
}
}
I tried a different version, you somehow need to check what you get back and handle each result individually so this is as good as I can get it. This compiles and executes fine.
func fetchOne<T: NSManagedObject>(_ objectType: T.Type) throws -> [T]? {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = objectType.fetchRequest()
return try managedObjectContext.fetch(fetchRequest) as? [T]
}
func doFetchAll() {
for entity in entities {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = entity.fetchRequest()
do {
if let result = try fetchOne(entity) {
if result is [InstrumentData] {
print(result[0].value(forKey: "name"))
}
}
} catch {
print(error)
}
}
}
Check whether Swift object is an instance of a given metatype
Unfortunately, you can currently only use a named type with the is
operator, you cannot yet use an arbitrary metatype value with it (although really IMO you ought to be able to).
Assuming you have control over the creation of the metatypes that you want to compare against, one solution that achieves the same result would be to create a wrapper type with an initialiser that stores a closure that performs the is
check against a generic placeholder:
struct AnyType {
let base: Any.Type
private let _canCast: (Any) -> Bool
/// Creates a new AnyType wrapper from a given metatype.
/// The passed metatype's value **must** match its static value,
/// i.e `T.self == base`.
init<T>(_ base: T.Type) {
precondition(T.self == base, """
The static value \(T.self) and dynamic value \(base) of the passed \
metatype do not match
""")
self.base = T.self
self._canCast = { $0 is T }
}
func canCast<T>(_ x: T) -> Bool {
return _canCast(x)
}
}
protocol P {}
class C : P {}
class D : C {}
let types = [
AnyType(P.self), AnyType(C.self), AnyType(D.self), AnyType(String.self)
]
for type in types {
print("C instance can be typed as \(type.base): \(type.canCast(C()))")
print("D instance can be typed as \(type.base): \(type.canCast(D()))")
}
// C instance can be typed as P: true
// D instance can be typed as P: true
// C instance can be typed as C: true
// D instance can be typed as C: true
// C instance can be typed as D: false
// D instance can be typed as D: true
// C instance can be typed as String: false
// D instance can be typed as String: false
The only limitation of this approach is that given we're performing the is
check with T.self
, we have to enforce that T.self == base
. For example, we cannot accept AnyType(D.self as C.Type)
, as then T.self
would be C.self
while base
would be D.self
.
However this shouldn't be a problem in your case, as we're just constructing AnyType
from metatypes that are known at compile time.
If however you don't have control over the creation of the metatypes (i.e you get handed them from an API), then you're quite a bit more limited with what you can do with them.
As @adev says, you can use type(of:)
in order to get the dynamic metatype of a given instance, and the ==
operator to determine if two metatypes are equivalent. However, one problem with this approach is that it disregards both class hierarchies and protocols, as a subtype metatypes will not compare equal with a supertype metatypes.
One solution in the case of classes is to use Mirror
, as also shown in this Q&A:
/// Returns `true` iff the given value can be typed as the given
/// **concrete** metatype value, `false` otherwise.
func canCast(_ x: Any, toConcreteType destType: Any.Type) -> Bool {
return sequence(
first: Mirror(reflecting: x), next: { $0.superclassMirror }
)
.contains { $0.subjectType == destType }
}
class C {}
class D : C {}
print(canCast(D(), toConcreteType: C.self)) // true
print(canCast(C(), toConcreteType: C.self)) // true
print(canCast(C(), toConcreteType: D.self)) // false
print(canCast(7, toConcreteType: Int.self)) // true
print(canCast(7, toConcreteType: String.self)) // false
We're using sequence(first:next:)
to create a sequence of metatypes from the dynamic type of x
through any superclass metatypes it might have.
However this method still won't work with protocols. Hopefully a future version of the language will provide much richer reflection APIs that allow you to compare the relationship between two metatype values.
However, given the above knowledge of being able to use Mirror
, we can use it to lift the aforementioned restriction of T.self == base
from our AnyType
wrapper on by handling class metatypes separately:
struct AnyType {
let base: Any.Type
private let _canCast: (Any) -> Bool
/// Creates a new AnyType wrapper from a given metatype.
init<T>(_ base: T.Type) {
self.base = base
// handle class metatypes separately in order to allow T.self != base.
if base is AnyClass {
self._canCast = { x in
sequence(
first: Mirror(reflecting: x), next: { $0.superclassMirror }
)
.contains { $0.subjectType == base }
}
} else {
// sanity check – this should never be triggered,
// as we handle the case where base is a class metatype.
precondition(T.self == base, """
The static value \(T.self) and dynamic value \(base) of the passed \
metatype do not match
""")
self._canCast = { $0 is T }
}
}
func canCast<T>(_ x: T) -> Bool {
return _canCast(x)
}
}
print(AnyType(D.self as C.Type).canCast(D())) // true
The case where T.self
is a class metatype should be the only case where T.self != base
, as with protocols, when T
is some protocol P
, T.Type
is P.Protocol
, which is the type of the protocol itself. And currently, this type can only hold the value P.self
.
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.
Related Topics
Determine If Any.Type Is Optional
Dynamic Row Hight Containing Texteditor Inside a List in Swiftui
Intrinsiccontentsize() - Method Does Not Override Any Method from Its Superclass
How to Left Align the Title of a Navigation Bar in Xcode
Use Queue and Semaphore for Concurrency and Property Wrapper
Swift Extension: Same Extension Function in Two Modules
Swift Nspredicate Throwing Exc_Bad_Access(Code=1, Address=0X1) When Compounding Statements
How to Dispatch Functions in Swift the Right Way
Is There a Prefix Header (Or Something with This Functionality) in Swift
Sort Array by Calculated Distance in Swift
Is Swiftui Backwards-Compatible With iOS 12.X and Older
Swiftui: Pop to Root View When Selected Tab Is Tapped Again
Multiple Workers in Swift Command Line Tool
Retrieve Only 5 Users At a Time :Firebase [Like Instagram]
Get the Top Viewcontroller in iOS Swift
Swift 2 iOS 9 Do Catch Try Crashing with Unexpected Nil Found