Self versus self in Swift 4 – capital “S” and lowercase “s”

RELATED: Find out how to make a copy of a class instance (object) using a C++-like copy constructor — a copy initializer in Swift.

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:

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:

The playground output from the previous code gives us:

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:

The playground output from the previous code gives us:

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:

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.


The playground output to console looks like this:

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:”

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:

I personally feel that the following code would look awful:

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:

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:

Here’s the playground console output from the previous code snippet:

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.

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:

Here’s the playground console output from the previous code snippet:

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.

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!

Author: Andrew Jaffee

Avid and well-published author, software engineer, designer, and developer, now specializing in iOS mobile app development in Objective-C and Swift, but with a strong background in C#, C++, .NET, JavaScript, HTML, CSS, jQuery, SQL Server, MySQL, Oracle, Agile, Test Driven Development, Git, Continuous Integration, Responsive Web Design, blah, blah, blah ... Did I miss any fad-based catch phrases? My brain avatar was kindly provided by http://icons8.com under a Creative Commons Attribution-NoDerivs 3.0 Unported license.

Leave a Reply

Your email address will not be published. Required fields are marked *