Why Must Constructing an Object of Class Type 'Someclass' with a Metatype Value Use a 'Required' Initializer

Why must constructing an object of class type 'someClass' with a metatype value use a 'required' initializer?

Consider the case where we also have a subclass:

class SomeClass {

}

class SomeSubclass : SomeClass {

}

If you store the class type in a variable:

var anotherClass = SomeClass.self

The variable anotherClass is of type SomeClass.Type.

You can later assign this variable to a subclass:

anotherClass = SomeSubclass.self

This is valid because SomeSubclass.Type is a SomeClass.Type. At this point, anotherClass() would fail if the initializer is not implemented in the subclass. This is what the compiler is protecting against.

In your sample code, this is impossible: you used let instead of var so changing the type is impossible. It may be that the safety checks just aren't nuanced enough to notice this.

Using metatype in closure

I think I found a solution, I removed the return type to make it implicit and it worked. Probably it is some kind of swift compiler bug. End result:

let type = String.self
let closure = { () in
return type.init()
}

Using type as a value, why is the self keyword required here?

Self-answering, with help of comments, I was able to find out the reason:


Using .self after the type name is called Postfix Self Expression:

A postfix self expression consists of an expression or the name of a
type, immediately followed by .self. It has the following forms:

expression.self
type.self

The first form evaluates to the value of the expression. For example, x.self evaluates to x.

The second form evaluates to the value of the type. Use this form to access a type as a value. For example, because SomeClass.self evaluates to the SomeClass type itself, you can pass it to a function or method that accepts a type-level argument.

Thus, the .self keyword is required to consider the type as a value capable of being passed as an argument to functions.

How to model a more limited version of class with shared code?

You can use a protocol with an associated type:

protocol Shelter {
associatedtype AnimalType

func take(x: AnimalType)
}

extension Shelter {
func usefulMethod(...)
}

class AnimalShelter : Shelter {
typealias AnimalType = Animal
func take(x: Animal) { ... }
}

class DogShelter : Shelter {
typealias AnimalType = Dog
var dogMedianCuteness: String = "normal (= very cute)"
func take(x: Dog) {}
}

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 like Int.self which will create a
    static metatype instance Int.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 { ... }


Related Topics



Leave a reply



Submit