Those of you who’ve used Objective-C and Swift for any meaningful length of time must be familiar with the self
property of structs and classes. I’m not sure how many are aware of the Self
“type” (sometimes called a “requirement”). I would be very interested in knowing how many understand the difference between self
and Self
. I’m talking about self
with lower-case “s,” which I’ll call “small self” herein. It’s pretty well documented. Similarly, Self
with an upper-case “S,” is what I’ll call “tall self” herein. It’s not very well documented.
A note on terminology
You’ll find that I follow Swift’s naming convention for “instances” on this blog:
An instance of a class is traditionally known as an object. However, Swift structures and classes are much closer in functionality than in other languages, and much of this chapter describes functionality that applies to instances of either a class or a structure type. Because of this, the more general term instance is used.
Small self – the self
variable
Small self, self
, should be straightforward to understand. It refers to an instance of a class or struct at both compile time and runtime. It literally connotes the “is-ness” of the class or struct itself in an existential sense. Small self is best defined by example. From the Swift docs:
Every instance of a type has an implicit property called self
, which is exactly equivalent to the instance itself. You use the self
property to refer to the current instance within its own instance methods.
I’ll create a class that represents a geometric point on a two-dimensional x, y axis which includes a default and designated initializer. This is similar to what I did in my previous tutorial:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class Point { var x: Int var y: Int // Don't need self because there // are no other x and y variables // to which we could be referring. init() { x = 0 y = 0 } // self is necessary here to // determine to which x and y // we are referring. init(x: Int, y: Int) { self.x = x self.y = y } } // end class Point |
I hope you read the two comments I added, one to the default initializer and the other to the designated initializer. Small self is only required in two cases: for disambiguation and closures. Let’s start with disambiguation. We’ll also touch upon when to use self
for readability, which is purely optional.
Disambiguation
In the default initializer, there’s no ambiguity as to which x
and y
variables to which I’m referring.
In the designated initializer, notice what happens if I leave out self
:
If the compiler tried to assume my intent, it could get into all sorts of trouble. In this case, the Swift compiler is making a narrow decision and only considering the local x
and y
arguments — constants in this case. Remember that for Swift, “Function parameters are constants by default.” So while the error message could be better than “Cannot assign to value: ‘x’ is a ‘let’ constant,” the answer here is to take away any ambiguity by using self.x
and self.y
on the left-hand side of both assignment statements in the designated initializer.
Note that I just showed you the use of small self with classes, which represent reference semantics and reference types, they’re allocated on the heap, and self
is basically a pointer, though Apple prefers the term “reference.”
Be reassured that self
works just about the same with structs, in other words when using value semantics and value types, with the caveat that an instance of a struct acts like a value, is allocated on the stack, and is not a reference:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
struct Point { var x: Int var y: Int let tag: String init() { self.x = 0 self.y = 0 self.tag = "N/A" } init(x: Int, y: Int, tag: String = "N/A") { self.x = x self.y = y self.tag = tag } func show() { print("\n--------------") print(self) print("(\(x), \(y))") print("(\(self.x), \(self.y))") } } // end struct Point let point1 = Point(x: 1, y: 2) point1.show() |
The playground output from the previous code gives us:
1 2 3 4 |
-------------- Point(x: 1, y: 2, tag: "N/A") (1, 2) (1, 2) |
Notice that print(self)
shows us a more value-like rendering of the struct instance — at least to me. It looks like a value.
I added the same show()
method to our class version of Point
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Point { ... func show() { print("\n--------------") print(self) print("(\(x), \(y))") print("(\(self.x), \(self.y))") } } // end class Point let point = Point(x: 2, y: 3, tag: "point") print(point.listTuple()) point.updateInBackground() print(point.listTuple()) point.show() |
The playground output from the previous code gives us:
1 2 3 4 5 6 7 8 |
point: (2, 3) point: (5, 3) -------------- __lldb_expr_13.Point (5, 5) (5, 5) point async update |
Notice that print(self)
shows us a more reference-like rendering of the class instance — at least to me. The printed expression, “__lldb_expr_13.Point”, looks like a reference. (My background is colored by being a C++ developer for so long.)
Closures
When reading the Swift documentation, you’ll see some example code and a “NOTE” referring to that code that states:
Swift requires you to write self.someProperty
or self.someMethod()
(rather than just someProperty
or someMethod()
) whenever you refer to a member of self
within a closure. This helps you remember that it’s possible to capture self by accident.
Regardless of whether you understand the concept of “capture” or not, you see that self
is required in closures.
Let me add a constant tag
member property to the Point
class to help identifying each instance during development and debugging. I’ll also add two closures. If you don’t know what a closure is, then I suggest you find out using my tutorials, here and here. They’re very useful.
Here’s my new code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class Point { var x: Int var y: Int let tag: String init() { x = 0 y = 0 tag = "N/A" } init(x: Int, y: Int, tag: String = "N/A") { self.x = x self.y = y self.tag = tag } lazy var listTuple: () -> String = { return "\(tag): (\(x), \(y))" } func updateInBackground() { DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { x = 5 y = 5 print("\(tag) async update") } } } // end class Point |
Notice the error messages, of the form “Reference to property ‘x’ in closure requires explicit ‘self.’ to make capture semantics explicit,” I received after I added the previous code:
In these cases, and based on my previous revelations to you, fixing these lines and understanding the fix should be obvious.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
class Point { var x: Int var y: Int let tag: String init() { x = 0 y = 0 tag = "N/A" } init(x: Int, y: Int, tag: String = "N/A") { self.x = x self.y = y self.tag = tag } lazy var listTuple: () -> String = { return "\(self.tag): (\(self.x), \(self.y))" } func updateInBackground() { DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { self.x = 5 self.y = 5 print("\(self.tag) async update") } } } // end class Point let point = Point(x: 2, y: 3, tag: "point") print(point.listTuple()) point.updateInBackground() print(point.listTuple()) |
The playground output to console looks like this:
1 2 3 |
point: (2, 3) point: (5, 5) point async update |
But that output can actually vary and be unpredictable. Do you know why? I’m using an asynchronous call whose results are stochastic. I’m using a background thread. If you don’t understand what I’m talking about, you really need to do some research. You’ve got to understand background threads. Here’s what I mean by “unpredictable:”
1 2 3 |
point: (2, 3) point: (5, 3) point async update |
Readability
Sometimes it’s beneficial to use little self for the purposes of increasing the readability of code but in which it is not a requirement. Sometimes adding self
is just gratuitous and just takes up space. In a few cases, self
is just required. According to the Swift documentation:
In practice, you don’t need to write self
in your code very often. If you don’t explicitly write self
, Swift assumes that you are referring to a property or method of the current instance whenever you use a known property or method name within a method. …
The main exception to this rule occurs when a parameter name for an instance method has the same name as a property of that instance. In this situation, the parameter name takes precedence, and it becomes necessary to refer to the property in a more qualified way. You use the self property to distinguish between the parameter name and the property name.
That’s a pretty obvious and straightforward rule.
Sometimes use of self
helps to differentiate between a class’s or struct’s member properties and, for example, similarly-named local variables and/or contants inside a member method. You might have a complex algorithm containing intermediate or temporary values of a member property which are named using some part of the member property name to which they relate. In that case, using little self may actually increase readability by emphasizing the difference between member property values and those of similarly-named local variable/constants containing intermediate or temporary values of a member property.
Immediately below, I show an example where I would not use self
. I believe it would add to code clutter and decrease the readability of the algorithm I encoded, the Pythagorean Theorem (see here and here), used for calculating the length of a line:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
class Line : IsEqual, Comparable { var beginPoint: CGPoint var endPoint: CGPoint ... init(beginPoint: CGPoint, endPoint: CGPoint) { self.beginPoint = CGPoint( x: beginPoint.x, y: beginPoint.y ) self.endPoint = CGPoint( x: endPoint.x, y: endPoint.y ) } // The line length formula is based on the Pythagorean theorem. func length () -> CGFloat { let length = sqrt( pow(endPoint.x - beginPoint.x, 2) + pow(endPoint.y - beginPoint.y, 2) ) return length } static func == (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool { return (leftHandSideLine.length() == rightHandSideLine.length()) } static func != (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool { return (leftHandSideLine.length() != rightHandSideLine.length()) } static func > (leftHandSideLine: Line, rightHandSideLine: Line) -> Bool { return (leftHandSideLine.length() > rightHandSideLine.length()) } ... |
I personally feel that the following code would look awful:
1 2 3 4 5 6 7 8 |
... // The line length formula is based on the Pythagorean theorem. func length () -> CGFloat { let length = sqrt( pow(self.endPoint.x - self.beginPoint.x, 2) + pow(self.endPoint.y - self.beginPoint.y, 2) ) return length } ... |
Nonetheless, the debate about when to optionally use little self rages onwards. I advise you to trust your gut and use your intuition as to when to use self
. But if you want to join the argument, go to this link — and you can find many, many more on your own.
Tall self – the Self
type (or requirement)
Tall self, Self
, is bit harder to get used to and to understand as it’s not well-documented. According to the Swift compiler, “‘Self’ is only available in a protocol or as the result of a method in a class.” From the Swift docs: “Self refers to the eventual type that conforms to the protocol.” In my experience, Self
is most useful in protocols.
Protocols
Apple’s Swift documentation states that:
A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Any type that satisfies the requirements of a protocol is said to conform to that protocol. …
If you want to know all about protocols and protocol-oriented programming (POP), see my tutorials here and here, respectively.
In my last post, I discussed an essential but not well-known feature that can enhance the safety of reference types (classes): “Class copy constructors in Swift 4 for defensive copying.” Here’s a protocol I introduced to require that adopting classes implement copy initializers:
1 2 3 4 |
protocol Copyable { init(copy: Self) } |
Since a protocol is a contract that requires any adopting class or struct to implement certain features, when we use tall self in a protocol, it’s sometimes called a “Self requirement.”
I made a modified version of Point
class adopt Copyable
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
protocol Copyable { init(copy: Self) } class Point: Copyable { var x: Int var y: Int var tag: String // now writable init() { x = 0 y = 0 tag = "N/A" } init(x: Int, y: Int, tag: String = "N/A") { self.x = x self.y = y self.tag = tag } required init(copy: Point) { self.x = copy.x self.y = copy.y self.tag = copy.tag } lazy var listTuple: () -> String = { return "\(self.tag): (\(self.x), \(self.y))" } } // end class Point let point = Point(x: 2, y: 3, tag: "point") print(point.listTuple()) // point: (2, 3) let pointCopy = Point(copy: point) pointCopy.tag = "pointCopy" pointCopy.x = 7 print(pointCopy.listTuple()) // pointCopy: (7, 3) print(point.listTuple()) // point: (2, 3) |
Here’s the playground console output from the previous code snippet:
1 2 3 |
point: (2, 3) pointCopy: (7, 3) point: (2, 3) |
Note that the copy initializer let me make of copy of the Point
instance called point
. I named the copy pointCopy
. Changes to pointCopy
did not affect the original point
instance.
Classes
Remember earlier that I quoted the Swift compiler as stating that “‘Self’ is only available in a protocol or as the result of a method in a class.” We just looked at protocols and Self
. Let me now combine the concepts of tall self in protocols and “as the result of a method in a class.”
I modified my original version of the protocol requiring a copy initializer, Copyable
, changed the way it works, and renamed it Copying
. Here’s the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
protocol Copying { func copy() -> Self } class Point: Copying { var x: Int var y: Int var tag: String init() { x = 0 y = 0 tag = "N/A" } required init(x: Int, y: Int, tag: String = "N/A") { self.x = x self.y = y self.tag = tag } func copy() -> Self { return type(of: self).init(x: self.x, y: self.y, tag: self.tag) } lazy var listTuple: () -> String = { return "\(self.tag): (\(self.x), \(self.y))" } } // end class Point let point = Point(x: 5, y: 5, tag: "point") print(point.listTuple()) // point: (5, 5) let pointCopy = point.copy() pointCopy.tag = "pointCopy" pointCopy.x = 11 print(pointCopy.listTuple()) // pointCopy: (11, 5) print(point.listTuple()) // point: (5, 5) |
Here’s the playground console output from the previous code snippet:
1 2 3 |
point: (5, 5) pointCopy: (11, 5) point: (5, 5) |
Note that the copy initializer let me make of copy of the Point
instance called point
. I named the copy pointCopy
. Changes to pointCopy
did not affect the original point
instance.
When I first implemented the protocol-required copy initializer func copy() -> Self
, I received the error message: “Constructing an object of class type ‘Self’ with a metatype value must use a ‘required’ initializer.” So I prefixed the initializer with signature init(x: Int, y: Int, tag: String = "N/A")
with the required
keyword. This ensures that any subclasses of Point
fulfill the copy initializer requirement specified in Copying
protocol.
I really don’t want to get too existential about these protocol-based requirements as they can get very complicated, convoluted, and almost existential.
Conclusion
After reviewing all my code and reading my long-winded discussion, you should understand the difference between self
and Self
. It is essential that you understand the difference. While understanding self
usually comes naturally, the use of Self
is almost inextricably linked with protocol-oriented programming. According to Apple, “at its heart, Swift is protocol-oriented.” They’re right and Swift is being pushed in this direction. Many developers are studying POP principles. So now that you understand Self
, dive in there!
You said “ Note that the copy initializer let me make of copy of the Point instance called point. I named the copy point1. Changes to point1 did not affect the original point instance.”
Don’t you mean pointCopy and not point1?
Yes. Thanks for catching that. I meant
pointCopy
and have updated the article.