Some best practices for designing and coding Swift classes

My original article — “Best Practices for Building Swift Classes” — was published on appcoda.com.

Follow along as I write this tutorial’s Swift code in a playground! Download from GitHub.

In this tutorial, I’m going to show you some best practices that will help you design and implement classes (reference types) and then safely leverage reference semantics in Swift. Protocol-oriented programming (POP) and value semantics are all the rage now, but a promising new technology doesn’t mean you should throw all your classes away. Why not add some simple constructs to your classes like copy initializers, default initializers, designated initializers, failable initializers, deinitializers, and conformance to the Equatable protocol? To get real about my sample code, I’ll adopt these constructs in some classes and show you my best practices working in real life for drawing in your iOS app interfaces.

I’ll walk through the process of creating several protocols, creating classes that adopt those protocols, implement inheritance in these classes, and use instances of the classes (objects), all to illustrate my best practices — and to show some of the extra steps you may have to go through when working with classes.


A NOTE ON TERMINOLOGY: By convention, when a class is created/initialized, and memory allocated, we’re said to have an “object” or “instance” of that class. I use the two terms interchangeably as I’m striving, as is Apple, to eventually only use the term “instance,” but recognize that many people still prefer the term “object.”

Using protocols as much as possible

Defining and using protocols makes your intent crystal clear when defining new classes. When you and other developers look at the signature for your classes, you get meaningful information immediately (like “this class supports a copy constructor”). In general, protocols are most effective and supportable when they stipulate no more than a singular requirement, or at most one singular category of requirements. Keep them as simple as you can. Divide and conquer. Since I break my protocols up into focused sets of requirements, my classes can adopt as many or as few protocols as needed.

POP, OOP, and POOP?

Remember one very important point: Most code in the iOS (and OS X) SDKs comes in the form of class hierarchies. I believe many of the core frameworks we use are still written in Objective-C (and some C++ and C), like Foundation and UIKit.

Think about creating a new Xcode project based on the iOS Single View App template. Where do many developers get started in this new type of project? Right, in file ViewController.swift. And what do we all see when opening that file? We see ViewController which is a subclass of UIViewController.

What does Apple have to say about ViewController? Here we go:

…You rarely create instances of the UIViewController class directly. Instead, you subclass UIViewController and add the methods and properties needed to manage the view controller’s view hierarchy. …

Subclassing Notes
Every app contains at least one custom subclass of UIViewController. More often, apps contain many custom view controllers. Custom view controllers define the overall behaviors of your app, including the app’s appearance and how it responds to user interactions.

Yeah, yeah, yeah… Apple is pushing protocol-oriented programming (POP), value types (structs and enums), and value semantics. Got it. Heard it loud and clear. But classes (reference types), with their reference semantics and object-oriented programming (OOP) capabilities will be around for awhile.

There’s always some new programming fad. The only thing I’m concerned about is whether POP is really useful or just another marketing ploy. So far, it has proven useful in my production code, but not all of the time.

THE THING ABOUT CLASSES

Reference types (classes) and reference semantics can be boon or bane, depending on how you use them. But that’s true of all prominently-used technologies. There are advantages and disadvantages. To paraphrase Shakespeare, “To copy or not to copy: that is the question.” My answer to the question is to first understand the problem at hand, trust my intuition, and use copying — value semantics — when necessary, and to use pointers, or as Apple prefers, references — reference semantics — when necessary.

Even with Apple’s supposed move towards POP, one of the Apple engineers at WWDC 2015 pointed out that “So, for example, a Window. What would it mean to copy a Window?” It wouldn’t mean anything except confusion. Indeed, what would a copy of a UIViewController subclass instance mean and what would you do with it? Entities like UIViewController and NSWindow should remain as classes.

Suppose you used the facade design pattern to create a simple interface and wrapper for a very complex database system that must be used by all customers who’ve bought your app. The most straightforward way to sensibly use this database would be to pass a reference to it around your app. Why in G-d’s name would you write a value type (struct) version of this database and give a copy of the database infrastructure and all its data to each instance of your app? You’d have a disaster.

Obviously, classes still have a place in the software development world. And please don’t think that making a class a member property of a struct is the answer to your prayers. That member property will exhibit reference semantics.

The problem with classes

You all probably know what I’m going to talk about. Here it is from an old Swift blog post:

Copying a reference… implicitly creates a shared instance. After a copy, two variables then refer to a single instance of the data, so modifying data in the second variable also affects the original.”

Here’s an example of the potential perils of reference semantics: I’ll create a class instance, then declare a reference to it, then declare another reference to it, set the latter reference equal to the former reference, end up with two references to the same object/instance, change a member property on the latter reference, and see that change reflected in the one instance:

Output to console from the previous code snippet:

Since objects coordinate and coordinate1 both are references to the object created by the statement Coordinate(x: 2.0, y: 4.0), changes to either reference changes the one and only instance. The statement var coordinate1 = coordinate is an example of copying a reference.

Note my use of the logical === operator:

From the Swift docs, === “Returns a Boolean value indicating whether two references point to the same object instance.” Obviously, in this case, coordinate and coordinate1 refer to the same instance. Remember that “Equality is Separate From Identity” and “The identity of a class instance is not part of an instance’s value.”

In even the smallest of apps, I’ve seen errors occur because of “unintended sharing,” unintended mutation — whatever you want to call creating multiple references to the same object and changing a property in one of those references. This behavior can be especially difficult to debug in multi-threaded code involving reference types. But guess what? You can write bad code using value types.

Changes to the first instance of some struct and subsequent changes to copies could still be “unintended.” Any what if you end up with a bunch of copies of a struct? Is it semantically clear as to what your code is doing? Even Apple admits it: “The problem is that immutability has some disadvantages.”

We could get into a discussion of making our classes safe by using Apple’s version of “defensive copying” where we make all our classes adopt the NSCopying (and perhaps NSObject) protocol(s), and then make sure all our classes implement copy(with:) (mainly just copy()), and remember to call copy() constantly, on every assignment. And calling copy() requires a cast:

Be aware that defensive copying is all over Cocoa and Objective-C. There are still many developers who have to support entire legacy iOS and OS X codebases. And there are many of us who must interact with legacy iOS and OS X codebases. We’re not going to take the NSCopying tack in this tutorial. While I will show you some code for defensive copying, I will also show you many other techniques to make your use of classes safe.

GETTING STARTED WITH A PLAYGROUND

Let’s create a new Xcode 9 playground… Or just download it from GitHub! In Xcode, go to File -> New > -> Playground… and click iOS and the Single View template icon. Click the Next button, select a location for your new playground’s bundle, give your playground a name, and click the Create button.

Make some room in your playground where my comment “PUT SOME SPACES HERE” is located (shown below) for the class-based code I’m going to write during this tutorial, like so:

Now replace the “PUT SOME SPACES HERE” comments with the protocol named Copyable and class called Line, as shown below:

You should be able to tell that objects of this class can be used to represent geometric, straight lines by defining beginning points and endpoints. I’ve used the CGPoint class to represent my beginning points and endpoints so that this class is very compatible with drawing/graphics. Currently, the Line class only has a designated initializer.

BEST PRACTICES FOR CLASSES

Based on the evidence I’ve seen from the evolution of the Swift language, my own experiences with Swift, and observing the experiences of others developing with Swift, classes aren’t going away. OOP features, made possible by use of classes, like inheritance, virtual methods, and polymorphism, aren’t going away because they’re just too darn useful.

Let me show you some techniques I use to mitigate “unintended sharing” in classes — and also other approaches you can use just to write better and semantically clear class-based code. A lot of what I’m sharing with you here comes from my years of experience writing C++ code.

Copy constructor (copy initializer)

You may have noticed the Copyable protocol in the initial code I added to our playground. Unintended mutation is one of the biggest critiques of reference semantics, so we’re going to start by mitigating class mutation. Notice my use of Self with a capitalized “S” as opposed to self with a lowercase “s” in my Copyable protocol. This is no accident. From the Swift docs: “Self refers to the eventual type that conforms to the protocol.” If you’re unclear as to what differentiates Self from self, I suggest you study up on the subject.

Why not recognize the “problem” of “unintended sharing” outright and make a semantic statement about it with a protocol that requires conforming classes to implement a “copy constructor?” To climb out of my C++ past, I’ll use Swift terminology and say “copy initializer.” A copy initializer allows us to initialize an instance of a class using another instance of a class. A new object is created containing the same member properties as another object of the same type. A fully independent copy of an object is made by copying an object of the same type. Though the new object contains the same data as the original, a new instance (object) is created. The new object is not a reference to the original object. It is a copy and the semantics of the copy initializer makes your intent clear.

Making a copy of a class instance can give you a form of immutability — a baseline to which you can compare future changes to that instance, and/or a way to restore the instance’s state if something goes wrong later.

#ad

Notice that Swift almost seems to frown on making a copy of a reference type, i.e., a copy of an instance of a class, or, as some would rather put it, getting a copy of an object. I’m not talking about getting another reference to a class, I’m talking about getting an entire, separate copy of a class instance. This frowning on class copying is not an accident. Swift’s language architects want the syntax and semantics of the language to be crystal clear. They want developers to be confident that reference types and value types will both have 1) distinct and obvious meanings and that both types will 2) behave consistently. But still, why not be able to safely make a copy of a class instance? I’ll show you how in this tutorial by borrowing the copy constructor concept from C++. In Swift, we’d call this a “copy initializer.”

Swift is downright difficult about allowing copying of class instances:

It is not possible to overload the default assignment operator (=).

In C++, I would override the assignment operator for my class to allow deep class instance copying, thus helping to prevent unwanted changes. I’d also create a copy constructor. I can even turn off assignment in C++ by making my assignment operator private. Swift isn’t C++, but does support one of the ideas I’ve floated, but only if you build a custom version.

Let’s make my Line class adopt my Copyable protocol. Copyable requires a conforming class to implement a copy initializer:

I want copy() to instantiate a new object of my Line class with its current member property values and return that instance. It will do that, but only until I clear up the “Constructing an object of class type ‘Self’ with a metatype value must use a ‘required’ initializer” error message as shown in this image:

Remember that Line subclasses will inherit copy() and the method’s implementation is mandated by the Copyable protocol. copy() calls the designated initializer. Note that in the image above, the first character, i, of this init is underlined, telling me that it needs to be marked as required because, according to the official Swift documentation:

Write the required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer … You must also write the required modifier before every subclass implementation of a required initializer, to indicate that the initializer requirement applies to further subclasses in the chain. You do not write the override modifier when overriding a required designated initializer…

I marked the designated initializer as required and the code now compiles:

I wrote some test code. I’ve taken the output shown in the right pane of the playground corresponding one-to-one with each line of code in the editor and made it into a comment for several lines of my code:

I initialized an instance of Line and got a reference to it, line1. I got another reference to the Line instance, line2, made and change to one of its properties and, of course, the change is visible in both references.

I used the copy initializer to get an independent copy of my Line instance. That copy is in fact a unique instance. Changing the copy, lineCopy, has no effect on the original instance as referenced by line1 and line2.

To further prove my copy initializer, I compared line1 and line2 with “the triple-equals identical-to operator (===)”. I also compared lineCopy with line1 (and didn’t bother with line2, because it’s identical to line1):

Is the copy initializer an initializer?
The copy() method you just saw is technically not an initializer, but you see that while it’s not called init, it does call init. It’s a method that makes a separate, independent copy of a class instance. It doesn’t copy a reference, it copies the current contents of an instance’s properties and returns a fresh instance of classes that conform to Copyable.

This is a protocol that indeed requires a copy initializer, as technically defined in the Swift language:

While this would be my preferred methodology of requiring and creating copy initializers, I’ve found that it can cause, in my eyes, unwanted and undue complexities when using inheritance. I’m still working on it and will let you know if I get things functioning up to my standards of readability and maintainability.

Inheriting the copy initializer
Creating a descendant with a copy initializer can be little tricky, so let’s go ahead and do it to flesh out the concepts required. Let’s extend my Line to include some properties that makes it easier to draw the line to screen in an iOS app. I’ll call the descendant DrawableLine. While the following code looks pretty clean, it does have some issues — issues that, when watching me resolve, will lead you to a better understanding of the topics in this tutorial, and for classes in general:

I need to deal with three problems as described by the Swift compiler: “‘required’ initializer ‘init(beginPoint:endPoint:)’ must be provided by subclass of ‘Line’,” “Overriding declaration requires an ‘override’ keyword,” and “Constructing an object of class type ‘Self’ with a metatype value must use a ‘required’ initializer.” Here’s an image showing the error messages:

I’ll make the required changes and explain them with inline code commentary. I’ll also talk about them some more after showing you my revisions. Here’s the working Line descendant, DrawableLine:

Having to implement init(beginPoint:endPoint:) in DrawableLine is a small price to pay for the fact that we can keep creating valid copy initializers for each new subclass of Line. (And it’s a heck of a lot better than the side effects I’m still wrestling with when using init(copy: Self) and inheritance.)

Regarding the required keyword, remember that when a class adopts a protocol, it must conform to that protocolAND remember that a class can have… what are they called? Descendents. I once again refer you to the section entitled “Required Initializers” in the Swift documentation.


Let me test the DrawableLine copy initializer to make sure that changes to a copy of an instance do not affect that instance:

Drawing my line in a playground
Let’s draw my thinBlackLine to the simulator in the playground. Delete all the boilerplate code at the bottom of the playground starting with this line:

Replace it with the following:

If you need help drawing in a playground, click here. Run the playground and you’ll see my line in the “Live View” pane:

Designated initializers

Notice that I used designated initializers in my code samples so far because I’ve got “Class Inheritance and Initialization” on my mind. I suggest you always craft designated initializers unless there are very extenuating circumstances.

When writing classes, I almost always consider the possibility that I might use inheritance to extend those classes at a later time. First, consider that:

Initialization is the process of preparing an instance of a class, structure, or enumeration for use. This process involves setting an initial value for each stored property on that instance and performing any other setup or initialization that is required before the new instance is ready for use.

Some developers skip creating initializers by marking all class member properties as optional. While sometimes it’s necessary to use optional member properties, I try to avoid them as much as possible. Most of the time I find that I can design my classes with non-optional member properties. Swift’s designers knew that having a designated initializer increases a class’s readability, supportability, and potential for future extensibility. It gives you and other developers a lot of insight into your class’s purpose and your intent in writing that class. From the Swift docs:

Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.

Classes tend to have very few designated initializers, and it is quite common for a class to have only one. Designated initializers are “funnel” points through which initialization takes place, and through which the initialization process continues up the superclass chain.

Every class must have at least one designated initializer. In some cases, this requirement is satisfied by inheriting one or more designated initializers from a superclass…

Default initializers

My next best practice for designing good classes comes in the form of another simple protocol:

Obviously, DefaultInitializable requires conforming classes to implement an init() method, what you should recognize as a default initializer.

Suppose your class contains all optional properties but with no explicit default values. In other words, all your class’s properties will be nil right after the default initializer is called. Your new class instance (object) could potentially crash your app. I guarantee that no matter what, somebody at some time is going to initialize one of your classes like this:

Remember what Swift does in such cases:

Swift provides a default initializer for any structure or class that provides default values for all of its properties and does not provide at least one initializer itself. The default initializer simply creates a new instance with all of its properties set to their default values. …

A strictly optional property with no default value “automatically receives a default value of nil, even though this value is not written in the code.”

So what happens when the developer that uses the previous code snippet passes line, line.beginPoint, or line.endPoint to some complex custom code that doesn’t check for nil? The code crashes.

That’s why DefaultInitializable requires you implement a default initializer (init()). I want you to think through and make semantically clear, in code, the default values your class’s properties start out with when that class first becomes an object.

We’ll look at an example of DefaultInitializable in a little while. It’s a simple protocol and I really want to show you how you can compose together as many or as few protocols as needed when defining a class.

Failable initializers

Suppose I write an app that is utterly and completely dependent on my protocol-oriented iOS file management code, as I introduced in-depth in my “iOS file management with FileManager in protocol-oriented Swift 4” tutorial. In other words, my app is useless without the iOS file system and the capability for individual file manipulation. Shouldn’t a developer using my code get informed of file system problems? A user should certainly be informed if she/he can’t use an app — and that their iPhone/iPad file system is failing.

What about creating a class, struct, or even enum that has a failable initializer? From Apple:

It is sometimes useful to define a class, structure, or enumeration for which initialization can fail. This failure might be triggered by invalid initialization parameter values, the absence of a required external resource, or some other condition that prevents initialization from succeeding.

To cope with initialization conditions that can fail, define one or more failable initializers as part of a class, structure, or enumeration definition. You write a failable initializer by placing a question mark after the init keyword (init?). …

A failable initializer creates an optional value of the type it initializes. You write return nil within a failable initializer to indicate a point at which initialization failure can be triggered.

In the case of my iOS file management code, the failure I’m looking for would be the lack of an essential resource, the iOS file system (for a variety of reasons, including device hardware problems). After everything we’ve discussed in this tutorial, all the examples I’ve provided, and all the references (hyperlinks) I’ve given you, do I really need to explain the following code? Note that I’ve abbreviated my original protocol-based code for didactic purposes — i.e., so you can concentrate on checking for nil values and concentrate on how a failable initializer works. Here’s some working code that you could pop into an Xcode playground and see work immediately — and note the failable initializer on line 30:

Here’s the output (below) from the last 10 lines of sample code (shown immediately above):

Note that this code was run on macOS for simplicity’s sake, but will run fine under iOS.

#ad
ARC memory management

When dealing with classes, memory management is handled by ARC and there’s always the possibility for memory leaks. I’m not proposing a magic cure for leaks, but I’m providing you with an aid to track down leaks — and a simple guide to show you how your instances are behaving memory-wise. Here’s a protocol and extension that I hope you’ll find as useful as I do:

The tag variable gives you the opportunity to identify individual instances of your classes. I can’t magically conjure up a default initializer for your classes, nor can I require a call to deinit in a protocol. So you have to configure your classes to cooperate with my Allocatable protocol.

Here’s code where I made a parent class and its child class adopt both my DefaultInitializable and Allocatable protocols, including some helpful inline commentary:

The next code snippet shows you how my DefaultInitializable protocol ensures safe usage of class instances when created even only using their default initializers, i.e., here, line and drawableLine3. It also shows you how the designated initializer allowed me to create two fully-configured lines, drawableLine1 and drawableLine2. Note that whether I used the default or designated initializers, the instances were ready for drawing to screen:

Then I wrote code to render those lines in a CGContext

… and displayed those lines in the Simulator:

The following sample code will show you how, using local scope in a playground, my Allocatable protocol allows you to track ARC memory allocation and deallocation:

Here is the playground output to console:

Conformance to Equatable

You should always be able to determine if the properties of one instance of a class are the same as, or different than, those of another instance, especially because of the possibility of unintended mutation.

First of all, remember that you can always definitively determine if several different references of the same type refer to the same instance (object) using the “identical-to operator,” more commonly known as ===.

But suppose you’ve used your copy initializer to save a baseline of the values of you used to create the original object (or created two instances using identical initializer arguments). Suppose later you want to see how far the original object’s values have strayed from their baseline values.

Or consider that you’ve got many different instances of classes and you need the ability to compare their property values for some business requirement (e.g., to find a last name match in a group of object’s representing persons). Equatable is an essential protocol (tool) to have. If you don’t remember Equatable, see my explanation here and Apple’s here.

So I added Equatable conformance to my Line class (which already conforms to Allocatable and DefaultInitializable). I kept it simple when determining whether Line instances were equal (and note that Equatable provides !=). I used the Pythagorean Theorem (see here and here) to determine and compare the length of two lines. Here’s the new Line code, which is inherited by DrawableLine:

Here’s some code to test my Equatable implementation, including the playground’s evaluation of each statement in the right-hand pane as a comment next to each line below:

Look at the image above and the values I used in initializers in a previous code snippet above to compare and validate line lengths and results.

Conclusion

No matter what paradigm you’re using, you need to have a set of best practices. Anybody can take a good tool and screw it all up. Successful developers get to know a technology like OOP through study and practice, practice, practice — writing lots of code. They’re willing to learn from their mistakes and admit when they’re wrong. They’re also willing to listen to mentors and/or the collective wisdom of their peers and adopt best practices.

I’ve presented you with some best practices for classes using constructs like copy initializers, default initializers, designated initializers, deinitializers, failable initializers, and conformance to the Equatable protocol. I hope they help you. At least try them out. You can find an enormous amount of advice on OOP out there, even for a relatively new language like Swift.

Get out out there and experiment, practice, study, and be your best!

#ad

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 https://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 *