Skip to content

Commit ddc9fb0

Browse files
committed
added metatypes
1 parent c39a1ec commit ddc9fb0

File tree

7 files changed

+292
-0
lines changed

7 files changed

+292
-0
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import UIKit
2+
3+
var str = "Hello, playground"
4+
print (str)
5+
6+
struct Person{
7+
var name: String
8+
init(_ name: String) {
9+
self.name = name
10+
}
11+
}
12+
13+
let tina = Person("Tina")
14+
15+
print(tina.self) // Person(name: "Tina")
16+
print(type(of: tina)) // Person
17+
18+
19+
20+
21+
class SomeBaseClass {
22+
class func printClassName() {
23+
print("SomeBaseClass")
24+
}
25+
}
26+
class SomeSubClass: SomeBaseClass {
27+
override class func printClassName() {
28+
print("SomeSubClass")
29+
}
30+
}
31+
let someInstance: SomeBaseClass = SomeSubClass()
32+
// The compile-time type of someInstance is SomeBaseClass,
33+
// and the runtime type of someInstance is SomeSubClass
34+
type(of: someInstance).printClassName()
35+
// Prints "SomeSubClass"
36+
37+
var person: Person
38+
person = .init("Rob")
39+
40+
class Shape {}
41+
class Circle: Shape {}
42+
class Square: Shape {}
43+
44+
var shapeCounts = [ObjectIdentifier: Int]()
45+
shapeCounts[ObjectIdentifier(Shape.self)] = 0
46+
shapeCounts[ObjectIdentifier(Circle.self)] = 1
47+
shapeCounts[ObjectIdentifier(Square.self)] = 2
48+
49+
print(shapeCounts[ObjectIdentifier(Circle.self)]) // 1
50+
51+
52+
class MyClass {
53+
static func foo() {
54+
print("foo")
55+
}
56+
57+
func bar() {
58+
print("bar")
59+
}
60+
61+
required init() {
62+
}
63+
}
64+
65+
let myInstance = MyClass()
66+
67+
// Use .self to access the static metatype of MyClass
68+
MyClass.self.foo()
69+
70+
// Use .type to access the dynamic metatype of myInstance
71+
type(of: myInstance).init().bar()
72+
73+
// Create an instance of MyClass as an alternative to MyClass()
74+
MyClass.self.init()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//: [Previous](@previous)
2+
3+
import Foundation
4+
5+
class Person {
6+
var name: String
7+
var age: Int
8+
9+
init(name: String, age: Int) {
10+
self.name = name
11+
self.age = age
12+
}
13+
}
14+
15+
let person = Person(name: "John", age: 30)
16+
let personType = type(of: person)
17+
18+
for property in Mirror(reflecting: personType).children {
19+
print("\(property.label!): \(property.value)")
20+
}
21+
22+
23+
//: [Next](@next)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<playground version='6.0' target-platform='ios' buildActiveScheme='true'/>

Metatypes/MetatypesPlayground.playground/playground.xcworkspace/contents.xcworkspacedata

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>SchemeUserState</key>
6+
<dict>
7+
<key>MetatypesPlayground (Playground).xcscheme</key>
8+
<dict>
9+
<key>isShown</key>
10+
<false/>
11+
<key>orderHint</key>
12+
<integer>0</integer>
13+
</dict>
14+
</dict>
15+
</dict>
16+
</plist>

Metatypes/README.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Metatypes in Swift
2+
## Why .self and .type matter
3+
4+
# Before we start
5+
Difficulty: **Beginner** | Easy | Normal | Challenging<br/>
6+
7+
## Keywords and Terminology:
8+
Metatypes: the type of any type
9+
10+
## Prerequisites:
11+
* None
12+
13+
## Why
14+
In Swift, you might have noticed that some code uses `.self` and some uses `.type` for various uses which can seem rather opaque to the beginner.
15+
16+
These are useful, and should not simply be ignored however!
17+
18+
# A Simple example
19+
You might have a rather forced example as follows:
20+
21+
```swift
22+
struct Person{
23+
var name: String
24+
init(_ name: String) {
25+
self.name = name
26+
}
27+
}
28+
29+
let tina = Person("Tina")
30+
```
31+
32+
so tina is clearly an instance of `Person`.
33+
34+
We can see this with the following print statements:
35+
36+
```swift
37+
print(tina.self) // Person(name: "Tina")
38+
print(type(of: tina)) // Person
39+
```
40+
41+
# The overview
42+
Each instance of `Person` can be represented by two things, the Type of the metatype.
43+
44+
```swift
45+
Type: Person
46+
Metatype: Person.Type
47+
```
48+
49+
The Type here represents the type of an instance, but the Metatype represents the metatype, which is a type that describes another type. In other words, the metatype describes the structure and properties of the type (including methods and properties) but does not describe the actual values.
50+
51+
## When a function needs a type
52+
So if you are writing a function that accepts a type (i..e Person.Type) rather than an instance you can write Person.Type as the type of the parameter - and that parameter would be Person.self.
53+
54+
A common use of self is from the register function in tableview:
55+
56+
```swift
57+
tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "MyTableViewCell")
58+
```
59+
60+
where the register function itself asks for any class with the definition:
61+
62+
```swift
63+
func register(_ cellClass: AnyClass?, forCellReuseIdentifier identifier: String)
64+
```
65+
66+
or equally we use a decoder while decoding JSON:
67+
68+
```swift
69+
let decoder = JSONDecoder()
70+
let decoded = try decoder.decode(Model.self, from: json)
71+
```
72+
73+
where of course Model is a Struct that represents a model type.
74+
75+
## Referring to the type (the metatype)
76+
77+
If we want to refer to the type itself (in our example `Person`) rather than an instance of the type we can refer to it with a `.Type` suffix, and we call that the metatype.
78+
79+
That is, we might want to access the type of a thing, rather than the type of a particular instance.
80+
81+
Since `AnyClass` is actually:
82+
83+
```swift
84+
typealias AnyClass = AnyObject.Type
85+
```
86+
87+
this is reasonably easy to understand.
88+
89+
By using `type(of:)` you can access the metatype of a class
90+
`print(type(of: tina)) // Person`
91+
92+
and here you can also access static functions or variables on that particular metatype, as in Apples example reproduced here:
93+
94+
```swift
95+
class SomeBaseClass {
96+
class func printClassName() {
97+
print("SomeBaseClass")
98+
}
99+
}
100+
class SomeSubClass: SomeBaseClass {
101+
override class func printClassName() {
102+
print("SomeSubClass")
103+
}
104+
}
105+
let someInstance: SomeBaseClass = SomeSubClass()
106+
// The compile-time type of someInstance is SomeBaseClass,
107+
// and the runtime type of someInstance is SomeSubClass
108+
type(of: someInstance).printClassName()
109+
// Prints "SomeSubClass"
110+
```
111+
112+
## .self and .type
113+
Since `String` is a type, and a value of an instance of String can be represented as "Hello, World!" we can apply the same generally as `String.Type` is a type and `String.self` is a value of a metatype.
114+
115+
In fact `.self` is a static metatype, so is a compile time type of an object. `.type` refers to the dynamic metatype of a type so is a runtime type of an object.
116+
117+
To access a dynamic metatype you should use `type(of)` which gives you an object's runtime type.
118+
119+
```swift
120+
class MyClass {
121+
static func foo() {
122+
print("foo")
123+
}
124+
125+
func bar() {
126+
print("bar")
127+
}
128+
129+
required init() {
130+
}
131+
}
132+
133+
let myInstance = MyClass()
134+
135+
// Use .self to access the static metatype of MyClass
136+
MyClass.self.foo()
137+
138+
// Use .type to access the dynamic metatype of myInstance
139+
type(of: myInstance).init().bar()
140+
141+
// Create an instance of MyClass as an alternative to MyClass()
142+
MyClass.self.init()
143+
```
144+
145+
## Hashable?
146+
A metatype cannot usually be used as a key in one of Swift's dictionaries since they are not Hashable by default. 
147+
There are however ways to use them as keys in a dictionary.
148+
149+
One approach is to use ObjectIdentifier to convert the metatype into a hashable value.
150+
151+
Since Hashable is a protocol that is implemented by `ObjectIdentifier` you can use as in `ObjectIdentifier(MyType.Type)` produces a `Hashable` type. ObjectIdentifier implements `Hashable` in order to be used as a key in a dictionary.
152+
153+
```swift
154+
class Shape {}
155+
class Circle: Shape {}
156+
class Square: Shape {}
157+
158+
var shapeCounts = [ObjectIdentifier: Int]()
159+
shapeCounts[ObjectIdentifier(Shape.self)] = 0
160+
shapeCounts[ObjectIdentifier(Circle.self)] = 1
161+
shapeCounts[ObjectIdentifier(Square.self)] = 2
162+
163+
print(shapeCounts[ObjectIdentifier(Circle.self)]) // 1
164+
```
165+
The dictionary above is called `shapeCounts` which then uses an `ObjectIdentifier` as it's key type.
166+
167+
You might do this if you wish to work with a set of types that cannot be easily enumerated, for example a plugin where third-party modules can add new types to an application dynamically it could do so with a dictionary of all available types.
168+
169+
# Conclusion
170+
If you've any questions, comments or suggestions please hit me up on [Twitter](https://twitter.com/stevenpcurtis)

0 commit comments

Comments
 (0)